本章重点内容:
- 数据类型的详细介绍
- 整形在内存中的存储
- 浮点型在内存中的存储解析
我们知道一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。本篇博客的主要内容就是搞清楚数据到底在所开辟内存中是如何存储的。
比如,代码示例如下:
int a = 20;
int b = -10;
通过之前大家对C语言的认识与学习,我们知道为 a,b 分别分配四个字节的空间,那究竟在这些空间中是如何存储,跟着 FLASH 的步伐,一起来揭开整形在内存中的存储这个神秘面纱。
⚡原码、反码、补码
内存中存储的都是二进制的数据,并且计算机中整数有三种2进制的表示方式:原码,反码,补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”。
- 正数的原、反、补码都相同。
- 负整数的三种表示方法各不相同。
> 原码:直接将数值按照正负数的形式翻译成二进制就可以得到原码。
> 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
>补码:反码+1就得到补码。
接下来,我们一起来回归到博客开始的问题,a 和 b 在内存中到底是怎样存储的呢:
int a = 20;
//原码:0000 0000 0000 0000 0000 0000 0001 0100
//反码:0000 0000 0000 0000 0000 0000 0001 0100
//补码:0000 0000 0000 0000 0000 0000 0001 0100
int b = -10;
//原码:1000 0000 0000 0000 0000 0000 0000 1010
//反码:1111 1111 1111 1111 1111 1111 1111 0101
//补码:1111 1111 1111 1111 1111 1111 1111 0110
由于 a 的原码、反码和补码都相同,不方便观察到底是以哪种形式存储,而 b 是负数,它的原码、反码和补码各不相同,那观察起来就非常明显,因此我们将以 b 为例子来探究我们想要的结果:
我们先打开调试,选择内存监视器1,输入&b,选择行列为4,得到了如下数据:
我们发现计算机给出了一串16进制的数字 f6 ff ff ff ,仔细观察可以发现 b 的二进制的补码化为16进制按顺序可以得到 ff ff ff f6,惊奇的发现似乎存放的是补码,只是在这里是倒着放的。这里计算机展示的是以16进制方便展示,在存的时候是确确实实的二进制。
对于整形来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
⚡大小端介绍
我们可以看到对于a和b分别存储的是补码,但是发现顺序有点不对劲,写个代码再看一看吧:
int a = 0x11223344;
为了方便观察,我们将 a 的值写为16进制,运行调试后我们可以发现数据在内存中是倒着放的,那为什么会出现这样的现象呢?原来当任何一个数据在存储的时候如果大于一个字节,那么它在内存中就会有这样的存储顺序的问题,从而引申出了大端存储模式和小端存储模式。
什么是大端小端:
> 大端字节序存储模式:是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中。
> 小端字节序存储模式:是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
为什么有大端和小端:
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
⚡练习题
1. 简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(百度2015年系统工程师笔试题)
数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中。数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
程序设计基本思路:要想判断当前机器的字节序,由大小端字节序存储模式的对比来看,只要选择一个合适的,有特点的数值,通过内存存储的首字节序与其数值本身首字节数值进行匹配比较,如果内存存储的低位首字节序预期数值本身低位首字节序相等,即可判断该机器为小端存储,否则即为大端存储。
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int check_sys()
{
int a = 1;
char* pa = (char*)&a;
if (*pa == 1)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
if (check_sys() == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
代码简化如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int check_sys()
{
int a = 1;
if (*(char*)&a == 1)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
if (check_sys() == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
再次对代码进行简化:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
if (check_sys() == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
运行结果如下:
2. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a = %d,b = %d,c = %d", a, b, c);
return 0;
}
代码分析:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = -1;
//原码:1000 0000 0000 0000 0000 0000 0000 0001
//反码:1111 1111 1111 1111 1111 1111 1111 1110
//补码:1111 1111 1111 1111 1111 1111 1111 1111
//因为是 char 类型,因此发生截断
// 1111 1111
//程序结果要求以%d形式打印,因此进行整型提升
//补码:1111 1111 1111 1111 1111 1111 1111 1111
//反码:1000 0000 0000 0000 0000 0000 0000 0000
//原码:1000 0000 0000 0000 0000 0000 0000 0001
// -1
signed char b = -1;
//VS编译器上 char == signed char
unsigned char c = -1;
//原码:1000 0000 0000 0000 0000 0000 0000 0001
//反码:1111 1111 1111 1111 1111 1111 1111 1110
//补码:1111 1111 1111 1111 1111 1111 1111 1111
//因为是 char 类型,发生截断
// 1111 1111
//由于是无符号char,因此整型提升时高位补0
//补码:0000 0000 0000 0000 0000 0000 1111 1111
//符号位为0,说明是一个正数,因此原码 = 反码 = 补码
//原码:0000 0000 0000 0000 0000 0000 1111 1111
// 255
printf("a=%d,b=%d,c=%d",a,b,c);
return 0;
}
运行结果如下:
3. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
unsigned int num = -10;
printf("%d\n", num);
printf("%u\n", num);
return 0;
}
%d:打印有符号数,结果是10进制的。
%u:打印无符号数,结果是10进制的。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
unsigned int num = -10;
//原码:1000 0000 0000 0000 0000 0000 0000 1010
//反码:1111 1111 1111 1111 1111 1111 1111 0101
//补码:1111 1111 1111 1111 1111 1111 1111 1010
printf("%d\n", num);
//%d:打印有符号数,根据变量的正负决定是否将内存中存的补码转换为原码来计算
//这里-10是负数,将补码转换为原码算出结果
//-10
printf("%u\n", num);
//%u:因为打印一个无符号数,因此将补码看作原码计算
//直接将补码看作原码计算结果
//4294967286
return 0;
}
运行结果如下:
4. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
代码分析:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = -128;
//原码:1000 0000 0000 0000 0000 0000 1000 0000
//反码:1111 1111 1111 1111 1111 1111 0111 1111
//补码:1111 1111 1111 1111 1111 1111 1000 0000
//存入char类型,发生截断
// 1000 0000
printf("%u\n",a);
//%u打印一个无符号整数,进行整型提升
//补码:1111 1111 1111 1111 1111 1111 1000 0000
//对于无符号数来讲,补码=反码=原码
//原码:1111 1111 1111 1111 1111 1111 1000 0000
// 4294967168
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = 128;
//原码:0000 0000 0000 0000 0000 0000 1000 0000
//反码:0111 1111 1111 1111 1111 1111 0111 1111
//补码:0111 1111 1111 1111 1111 1111 1000 0000
//存入char类型,发生截断
// 1000 0000
printf("%u\n",a);
//%u打印一个无符号整数,进行整型提升
//补码:1111 1111 1111 1111 1111 1111 1000 0000
//对于无符号数来讲,补码=反码=原码
//原码:1111 1111 1111 1111 1111 1111 1000 0000
// 4294967168
return 0;
}
运行结果如下:
5. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
运行结果如下:
6. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
代码分析:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
//由于 i 的类型为无符号整形,且以%u的形式打印
//因此当循环到 i==0 时,i--得到的是2^32-1
//继续进行循环,该循环为死循环
return 0;
}
运行结果如下:
7. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<windows.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
代码分析:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
//由于数组的类型为char,因此取值范围为-128~127
//由char取值图中可知
//-1 -2 -3……-128 127 126 ……3 2 1 0 -1 -2 -3……-127 -128 127
printf("%d", strlen(a));
//strlen函数求字符串长度,找到'\0'为止。'\0'ASCLL码值为0
return 0;
}
运行结果如下:
8. 求解以下代码输出什么?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
代码分析:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
//当i==255时,i++,得到256化为二进制得
// 1 0000 0000
//因为i是char类型,因此发生截断得到
// 0000 0000
//就是 0
//一直往复死循环下去
return 0;
}
运行结果如下:
感谢大家能够看完这篇博客,创作时长,小伙伴们觉得我的博客对你有帮助,不妨留下你的点赞的收藏,关注我,带你了解不一样的C语言。