例如一个int整型,我们知道它的大小为4个字节,一个字节为8个bit位。
如一个int a=10在内存中通过16进制和2进制分别表示为
//16进制表示
0x0000000a
//2进制
00000000 00000000 00000000 00001010
而如果是一个char a=10的话,在内存中用16进制和2进制分别表示为
//16进制表示
0x0a
//2进制表示
00001010
由此可知:
常见的数据类型有:
整数类型
charunsigned char signed char
short
unsigned short [int] signed short [int]
int
unsigned int signed int
long
unsigned long [int] signed long[int]
浮点类型
float
double
而数据在内存具体是怎么存储的,接下来将探讨这个问题。
在整数中的三种表示方法:
并且这些表示方法都拥有两个部分,符号位和数值位两部分,符号位用0表示正数,用1表示负数,数值位在负数的时候三种表示方法各不相同。
正数原码、反码和补码相同
如:
int a=10
int b=-10
00000000 00000000 00000000 00001010 //a=10 原码 反码 补码相同
10000000 00000000 00000000 00001010 //b=-10 原码
11111111 11111111 11111111 11110101 //b=-10 反码
11111111 11111111 11111111 11110110 //b=-10 补码
为什么整数在计算机中存的是补码?
因为在计算机中用补码存储可以将符号位和数值位统一处理。
也可以统一进行加减运算(CPU只能进行加法操作),并且原码与补码的转换方式是相同的,不需要额外的硬件电路
如果用原码进行1+(-1)的操作
00000000 00000000 00000000 00000001 //1原码
10000000 00000000 00000000 00000001 //-1原码
10000000 00000000 00000000 00000010 //原码相加-2 得不到0
如果用补码进行1+(-1)的操作
00000000 00000000 00000000 00000001 //1补码
10000000 00000000 00000000 00000001 //-1原码
11111111 11111111 11111111 11111110 //-1反码
11111111 11111111 11111111 11111111 //-1补码
1[00000000 00000000 00000000 00000000] //1补码和-1补码相加 得33位 但int只有32位所以为0
原码与补码的转换方式是相同的,都可以通过取反+1来得到对方
下面用Int a=-10 来举例
10000000 00000000 00000000 00001010 //-10原码
11111111 11111111 11111111 11110101 //-10反码
11111111 11111111 11111111 11110110 //-10补码
10000000 00000000 00000000 00001001 //补码取反
10000000 00000000 00000000 00001010 //+1得到原码
下面来看这样一个现象
int main()
{
int a = 10;
//00000000 00000000 00000000 00001010 2进制表示
//00 00 00 0a 16进制表示
return 0;
}
当我们在内存中搜索a的地址的时候发现,显示的0a 00 00 00 顺序并不和我们所表达的一样,
这是为什么呢?
上图中0a在低地址,数据低位保存在内存中的低地址中,所以是小端。
下面通过一个程序判断自己机器是大端还是小端:
a=10中存储为00 00 00 0a
如果是大端高地址存储低位 char类型截断低位得到00
如果是小端低地址存储低位 char类型截断低位得到0a
void Check(int a)
{
char* p = (char*)&a;
if (*p)
{
printf("小端");
}
else
{
printf("大端");
}
}
int main()
{
int a = 10;
Check(a);
return 0;
}
让我们用char来讨论一下
字符类型有三种,分别是:char(有编译器是signed char,有编译器是unsigned char,vs中是signed char ) 、signed char 、unsigned char。
signed char类型通过二进制码可以得知
范围是00000000~11111111,下面来看看具体十进制范围是多少
//补码形式
00000000 //0
00000001 //1
00000010 //2
...
01111111 //127(128-1)
-----------------
10000000 //-128 11111111 10000000 //符号位始终不变 进位被舍弃
10000001 //-127 11111110 11111111
10000010 //-126
...
11111111 //-1 10000000 10000001
所以signed char 的范围是-128~127
而unsigned char因为不用考虑符号位就是0~255(128+127)
下面来看一个例题,在vs环境下,所以char默认是signed char类型
int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
输出的结果是:255
我们已知signed char最低范围是-128,所以当a[127]=-128的时候,在进行-1,就会变为127。
所以直到a[255]=0,strlen(a)取断
//补码运算
10000000 -128补码
11111111 -1补码
1[01111111] -128+(-1)=127
127+(-1)
01111111
11111111
1[01111110] 126
...
00000000
并且由此可知,signed char 范围(-128~127)如果一直-1,似乎会进入一个循环,-128+(-1)=127,127+1=-128
浮点型主要是float和double,它们也是有自己的范围。
float在32位系统下是4个字节,在64位系统下是8个字节
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
它的运行结果如下
这是为什么呢?
明白它之前,让我们先来看看浮点数在内存中是如何存储的。
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
V=(-1)^S * M * 2^E
(-1) ^ s 表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
例如0.5 二进制表示为1.0 * 2^(-1)
(-1)^0 1.01 2^2
float的空间分配
在存入:
国际标准IEEE 754对E和M还有一些特别的规定:
在M中
因为1<=M<2,所以M可以写成1.xxxxxx,xxxxxx是小数的形式,因为在保存M=1.xxxxx的时候,第一位默认总是1,所以可以被省去,只保留后面小数部分,这样就可以多存一位。
在E中(首先E是一个无符号整数 unsigned int)
如果E只有8位,就代表范围是0~255,64位就是0 ~ 2047,但是E是可以出现负数的,所以所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
如存入一个9.0
先化为二进制9.0->1001->1.001*2^3
E中3+127为
00000011 //3
01111111 //127
10000010 //130
M中 001存入为
00100000000000000000000
则存入就为
0 10000010 00100000000000000000000 //总共32位
在取出:
E在取出时
1.如果E不全为1或者不全为0,那么在取出时要减去中间值-127,再将M加上1。
2.如果E全为0,这时E的指数位为1-127,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
3.如果E全为1,这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
//整数9在内存中存储
00000000 00000000 00000000 00001001
//那么如果通过浮点型读取
//V=(-1)^0*00000000000000000001001*2^(1-127)趋近于0 所以结果为0
0 00000000 00000000000000000001001
-------------------------------------
//将浮点型9.0存入 9.0->1001->(-1)^0 3 1.001
0 10000010 00100000000000000000000
//通过整型读取
01000001000100000000000000000000 //结果就是一个很大的数
//取出 就是9.0
了解数据在内存中的存储,对于出现一些不合常理的值时,能通过研究其内存布局从而判断其出现的原因。