文章目录
C语言笔记13:数据在内存中的存储
一、整数在内存中的存储
整数在内存中存放的是补码
补码:正数原码、反码、补码一样
负数:
原码是符号位为1
反码是原码除了符号位按位取反(其实反码和原码是一个性质)
补码是反码+1
存放补码的原因:可以使用加法来进行减法运算,补码是模运算
0的表示只有一个没有+0和-0
二、大小端字节序和字节序判断
什么是大小端
当一个数据比如short类型等,大小超过一个字节,就会有字节序的问题。
一个整数1存放在内存中:
大端字节序:

小端字节序:

练习
2.1判断当前机器大小端
c
int Check_Sys()//或者也可以叫做Is_Small()
{
int i = 1;
return (*(char*)&i);
}
int main()
{
int ret = Check_Sys();
if(ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");//ret==0
}
}
c
int Check_Sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;
}
2.2判断下面程序的运行结果
c
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d\n",a,b,c);
return 0;
}
字符型整数运算会发生整型提升。赋值的时候就是把寄存器中的4字节整型的-1最低位字节赋值给了三个变量,看这三个数据在内存中的情况:

printf("a=%d",a);这里会把a的值拿出来,放入寄存器中,由于a是一个char类型,在visual studio 2022下默认是有符号的类型,所以进行符号位扩展。所以在寄存器中ff扩展为ff ff ff ff同理b在寄存器中也扩展为了
ff ff ff ff而c是一个无符号数,进行0扩展,所以就是
00 00 00 ff
ff ff ff ff按照%d的形式打印就是-1
00 00 00 ff按照%d的形式打印就是255
结果:

2.3
c
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
-128一个字节时是1000 0000 由于a是有符号类型,符号位扩展为
ff ff ff 80,按照无符号打印,就是4,294,967,295 - 127 = 4,294,967,168。
结果:

c
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
按理来说,char类型的变量表示的数值范围是
0~127,-1~-128然而这里却用将128赋值给了a,那么实际是怎么运算的呢?其实赋值操作的时候是把四字节数据截断赋值给一个字节,虽然char类型不能表示128,但是int类型可以,int 类型的128用4字节表示就是00 00 00 80。截断之后就变成了80,然后按照%u打印,先将80进行整型提升,符号位是1,那就是ff ff ff 80,按照%u打印,就是4,294,967,168和上面一样。
运行结果:

2.4
c
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
for(int i = 0;i < 1000;i++)
{
a[i] = -1 - i;
}
printf("%zd\n",strlen(a));
return 0;
}
开辟一块空间,然后赋值。内存示意图:

问:什么时候数组a里的元素值等于0?
补码数值示意图:

char类型表示的负数范围是
-1~-128那-129会被如何解释?-129首先赋值是四字节的-129截断成一字节:ff ff ff 7f,也就是7f,也就是说,负数的值小于-128之后就会像图片一样往0靠近。所以从-1开始递减到0一共有256个数字,其中0不计,所以strlen就是255。
运行结果:

2.5
c
#include <stdio.h>
unsigned char i = 0;
int main()
{
for(i = 0;i <= 255;i++)
{
printf("hello world\n");
}
return 0;
}
所谓i++,其实是,i = i + 1;是一个赋值操作,unsigned char 类型的 i 表示的最大数应该是255,那么256是怎么表示的呢?四字节的256也就是00 00 01 00然后发生截断变成00,也就是说,对i一直++它的值也会像这个环一样循环:并且判断i <= 255的时候,进行了0扩展,所以寄存器中使用的值和它的值一样。

所以这个程序是个死循环。
运行结果:死循环打印hello world
c
#include <stdio.h>
int main()
{
unsigned int i;
for(i = 9;i >= 0;i--)
{
printf("%u\n",i);
}
return 0;
}
和上面一样,i的值不会被解释成小于0的数,所以这也是个死循环。先打印
9 ~ 0后打印极大的数递减。其实就是2^32 - 1开始递减。
2.6
c
#include <stdio.h>
int main()
{
int a[4] = {1,2,3,4};
int *ptr1 = (int*)(&a + 1);
int *ptr2 = (int*)((int)a + 1);
printf("%x,%x",ptr1[-1],*ptr2);
return 0;
}
内存示意图:小端字节序,x86地址。

ptr1[-1]应该是00 00 00 04
*ptr2应该是02 00 00 00
运行结果:

三、浮点数在内存中的存储
浮点数的表示:
一张图搞清楚:

上面的是float的存储,下面的是double,至于long double 类型,根据平台不同大小不一样。
-
S:0表示正数,1表示负数
-
M:当E不为全0时,M表示为
1.M,当E为全0时,M表示为0.M,E为全1时,如果M为全0就表示无穷大,否则就是NaN:Not a Number。 -
E:E是一个无符号整数,但是指数有负数,所以存入内存的E的值是
真实值+8/11位指数位的中间数(127/1023)。全0时为-126,全1时不为128。
为什么E为全0的时候,M就要表示为0.M呢?
我们来看两种方案:
方案1:
E为全0时,M表示为0.M
最小规格化数:1.0*2-126
最大非规格化数:0.111...11*2-126
最小非规格化数:0.000...000*2-126
方案2:
E为全0时,M依然表示为1.M
最小规格化数:1.0*2-126
最大非规格化数:1.111...111*2-127
最小非规格化数:1.000...000*2-127
对比之下我们可以发现,方案1和方案2都实现了规格化数和非规格化数的过渡,但是方案1可以一直表示接近甚至是等于0的值。而方案二却因为要保证
1.M而无法表示接近或者等于0的值。
什么是规格化和非规格化?
规格化就是保证精度但是牺牲表示范围,非规格化就是丢失一定的精度去保证表示更大的范围。
例题
c
#include <stdio.h>
int main()
{
int n = 9;
float *pFloat = (float*)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0f;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
00 00 00 09:0000 0000 0000 0000 0000 0000 0000 1001
S:0
E:0000 0000
M:0000 0000 0000 0000 0001 001
此时E为全0,M表示0.M,也就是0.0000 0000 0000 0000 0001 001 * 2-126。而float默认打印小数点后六位所以打印结果应该是0.000000
*pFloat = 9.0f;9.0表示为二进制就是1001.0也就是1.001*23,内存中就是:
S:0
E:1000 0010
M:0010 0000 0000 0000 0000 000
也就是:0100 0001 0001 0000 0000 0000 0000 0000 十六进制:41 10 00 00
打印成%d就是
1,091,567,616。
运行结果:
