ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深挖C语言:数据在内存中的存储

深挖C语言:数据在内存中的存储 目录1.整数在内存中的存储1.1 原码 反码 补码1.2 大小端字节序及其判断1.3 整型提升2. 练习1. 设计程序来判断当前机器的字节序2. 判断输出的结果及其原因3.截断与提升4. 判读数组长度5. 判断循环6. 判断输出结果3.浮点数在内存中的存储3.1 浮点数的存储3.1.1 浮点数存的过程3.1.2 浮点数取出的过程补充abs、fabs函数1.整数在内存中的存储1.1 原码 反码 补码含义原码 反码 补码 是整数的2进制的三种表示方法。1.原码直接将数值按照正负的形式翻译成二进制。2.反码将原码的符号位不变其他位次都按位取反。3.补码反码的最后一位加1 逢二进一。注1.有符号整数的三种表示方法都有符号位和数值位两部分第一位是符号位剩余均是数值位。符号位 0 表示“正” 1 表示“负”。整型是4个字节一个字节对应8个比特总共32位2.正整数的原反补码都相同。负整数的三种表示方法各不同。原码得到补码方法取反加一补码得到反码方法取反加一例如-10原码 10000000 00000000 00000000 00001010反码 11111111 11111111 11111111 11110101补码 11111111 11111111 11111111 11110110补码转原码10000000 00000000 00000000 00001010对于整型来说数据存放在内存中其实存放的是补码。原因在计算机系统中数值一律用补码来表示和存储。原因在于使用补码可以将符号位和数值域统一处理同时加 和减法也可以统一处理CPU只有加法器此外补码与原码相互转换 其运算过程是相同的不需要额外的硬件电路。1.2 大小端字节序及其判断什么是大小端超过一个字节的数据在内存中存储的时候就会有存储顺序的问题按照不同的存储顺序可以分为大端字节序存储和小端字节序存储具体概念如下1.大端存储模式是指数据的低位字节内容保存在内存的高地址而数据的高位字节内容保存在内存的低地址处。2.小端存储模式是指数据的低位字节内容保存在内存的低地址而数据的高位字节内容保存在内存的高地址处。1.3 整型提升定义C语言的隐式类型转换规则。所有长度小于 int 的整型charshort_Bool在参与运算、传入可变参数函数printf等时会自动转换为 int 类型。本质在二进制高位补充对应位数将短类型扩展为 int 长度保证数值语义不变。规则1.有符号类型signed char / signed short)高位补符号位(1)正数符号位为0高位全部补0(2)负数符号位为1高位全部补1目的提升数值的大小正负完全保持不变。2.无符号类型unsigned char / unsigned short(1)高位统一补0目的不存在符号位仅保证数值大小不变。3.截断、提升截断大类型→小类型丢弃高位。截断后符号位发生改变提升小类型→大类型补充高位。2.练习1. 设计程序来判断当前机器的字节序#includestdio.h int main() { int a 1; if(*(char*)a 1) { printf(是小端字节序\n); }else { printf(是大端字节序\n); } return 0; }2. 判断输出的结果及其原因#includestdio.h int main() { char a -1; // 原码 10000000 00000000 00000000 00000001 // 反码 11111111 11111111 11111111 11111110 // 补码 11111111 11111111 11111111 11111111 // a截断 - 11111111 signed char b -1; // 原码 1000000 00000000 00000000 00000001 // 反码 1111111 11111111 11111111 11111110 // 补码 1111111 11111111 11111111 11111111 // b截断 - 11111111 unsigned char c -1; // 原码 1000000 00000000 00000000 00000001 // 反码 1111111 11111111 11111111 11111110 // 补码 1111111 11111111 11111111 11111111 //11111111 - c printf(a %db %dc %d,a,b,c); // %d 是以十进制形式打印有符号整数。 //有符号char高位提升补 1 //无符号char高位提升补 0 // b提升 - 11111111 11111111 11111111 11111111 // c提升 - 00000000 00000000 00000000 11111111 return 0; }3.截断与提升#includestdio.h int main() { char a -128; // 原码10000000 00000000 00000000 10000000 // 反码11111111 11111111 11111111 01111111 // 补码11111111 11111111 11111111 10000000 // a截断10000000 printf(%u\n,a); // a提升11111111 11111111 11111111 10000000 return 0; }#includestdio.h int main() { char b 128; // 原反补码00000000 00000000 00000000 10000000 // b截断10000000 printf(%u\n,b); // b提升11111111 11111111 11111111 10000000 return 0; }4. 判读数组长度此处编译器不能运行代码建议使用vs2026运行#includestdio.h #includestring.h int main() { char a[1000]; for(int i 0;i 1000;i) { a[i] -1 - i; // -1 -2 -3 ... -128 127 126 ... 4 3 2 1 0 -128 -127... } printf(%d,strlen(a)); // 最终结果为 255 return 0; }char 类型由于只有一个字节8个比特位所有有符号char类型的数据取值范围为 -128~1275. 判断循环#includestdio.h unsigned char i 0; int main() { for(i 0;i 255;i) { printf(hello world\n); } //结果死循环 return 0; }#includestdio.h int main() { unsigned int i; for(i 9;i 0;i--) { printf(%u\n,i); } //结果死循环 return 0; }6. 判断输出结果(此处编译器会将 ptr2 判断为野指针建议使用vs2026运行#includestdio.h // x86环境 小端字节序 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; }3.浮点数在内存中的存储常见浮点数3.14、1E0等。代码练习#includestdio.h int main() { int n 9; // 原反补码00000000 00000000 00000000 00001001 float *pFloat (float*)n; printf(n的值为%d\n,n); printf(*pFloat的值为%f\n,*pFloat); // 0 00000000 00000000000000000001001 // 0.00000000000000000001001 * 2^-126 *pFloat 9.0; // 0 10000010 00100000000000000000000 9.0 printf(n的值为%d\n,n); // 01000001 00010000 00000000 00000000 1091,567,616 printf(*pFloat的值为%f\n,*pFloat); return 0; }上述 n 和 *pFloat 在内存中明明是同一个数为什么浮点数和整数的解读结果会差别这么大3.1 浮点数的存储根据国际标准IEEE电气和电子工程协会754任意一个二进制浮点数V可以表示成下面的形式V (−1) ^S∗M∗ 2^E1.(−1)^S表示符号位当S0V为正数当S1V为负数。2.M表示有效数字M是大于等于1小于2。3.2^E 表示指数位。举例十进制的5.0写成二进制是101.0相当于1.01*22。那么按照上面 V 的格式可以得出S0M1.01E2。IEEE 754规定对于32位的浮点数(float)最高的1位存储符号位S接着的8位存储指数E剩下的23位存储有效数字 M对于64位的浮点数(double)最高的1位存储符号位S接着的11位存储指数E剩下的52位存储有效数字 M3.1.1浮点数存的过程IEEE 754 对有效数字M和指数E还有一些特殊规定。前面说过1≤M 2也就是说M可以写成1.xxxxxx的形式其中xxxxxx表示小数部分。IEEE 754 规定在计算机内部保存M时默认这个数的第一位总是1因此可以被舍去只保存后面的xxxxxx部分。比如保存1.01的时候只保存01等到读取的时候再把第一位的1加上去。目的是节省1位有效数字。以32位浮点数为例留给M只有23位将第一位的1舍去以后等于可以保存24位有效数字。指数E首先E为一个无符号整数unsigned int这意味着如果E为8位它的取值范围为0~255如果E为11位它的取值范围为0~2047。但是我们知道科学计数法中的E是可以出现负数的所以IEEE 754规定存入内存时E的真实值必须再加上一个中间数对于8位的E这个中间数是127对于11位的E这个中间数是1023。比如2^10的E是10所以保存成32位浮点数时必须保存成10127137即10001001。注意到有的浮点数是有可能无法精确保存的。比如1.23.1.2浮点数取出的过程指数E从内存中取出还可以再分成三种情况1. E不全为0或不全为1常规情况这时浮点数就采用下面的规则表示即指数E的计算值减去127(或1023)得到真实值再将有效数字M前加上第一位的1。比如0.5 的二进制形式为0.1由于规定正数部分必须为1即将小数点右移1位则1.0*2^-1 其阶码为-1127(中间值) 126表示为01111110而尾数1.0去掉整数部分为0补齐0到23位 00000000000000000000000则其二进制表示形式为0 01111110 000000000000000000000002. E全为0这时浮点数的指数E等于1 - 127或者1 - 1023即为真实值有效数字M不再加上第⼀位的1而是还原为0.xxxxxx的小数。这样做是为了表示±0以及接近于0的很小的数字。0 00000000 001000000000000000000003. E全为1这时如果有效数字M全为0表示±无穷大正负取决于符号位s。0 11111111 00010000000000000000000有趣代码#includestdio.h int main() { if(0.1 0.2 0.3) { printf(\n); }else { printf(!\n); } return 0; }上述代码输出 ! 的原因就是部分浮点数无法精确保存优化代码如下#includestdio.h #includemath.h //设置浮点数比较的误差 #define EXP 0.000001 int main() { //如果浮点数比较误差在设定范围内则为真 if(fabs((0.1 0.2) - 0.3) EXP) printf(\n); else printf(!\n); return 0; }补充abs、fabs函数函数头文件参数类型返回值类型功能absstdlib.hintint计算整数的绝对值fabsmath.hdoubledouble计算双精度浮点数的绝对值其他长整型绝对值labs(long)、llabs(long long)同属stdlib.h其他精度浮点绝对值fabsf(float)、fabsl(long double)同属math.h最后感谢阅读欢迎交流与指正。
返回列表