数据在内存中的存储1https://developer.aliyun.com/article/1477743
整型提升
整型提升是指将较小的整数类型转换为较大的整数类型的过程。举个例子,如果有一个int类型的变量和一个char类型的变量进行加法运算时,char类型的变量会被提升为int类型再进行运算。这是因为int类型大于char类型,这样提升能有效避免精度的丢失。
整型提升的规则:
- 有符号整数提升按照数据类型的符号位来提升,提升时补符号位
- 无符号整数提升,高位直接补0
下面举两个整型提升的例子
例题1
//有符号整型提升 //负数整型提升 char a = -1; 内存中存储:11111111(8位) 整型提升后:11111111111111111111111111111111(32位) //正数整型提升 char a = 1; 内存中存储:00000001(8位) 整型提升后:00000000000000000000000000000001(32位) //无符号整型提升 unsigned char a = -1; 内存中存储:11111111(8位) 整型提升后:00000000000000000000000011111111(32位) unsigned char a = 1; 内存中存储:00000001(8位) 整型提升后:00000000000000000000000000000001(32位)
现在我们来看一道例题
#include <stdio.h> int main() { char a = -1; signed char b = -1; unsigned char c = -1; printf("%d %d %d\n", a, b, c); return 0; }
问:求以上程序的输出结果?
先看a再打印出来之前经历的转换
a 原码:10000001 反码:11111110 补码:11111111 打印时整型提升(补符号位,这时符号位为1): 11111111111111111111111111111111(此时这个二进制再计算机内存中,为补码) 打印原码:10000000000000000000000000000001 打印结果为:-1
由于signed char == char,所以b打印的结果也为-1
再来看c
c 原码:10000001 反码:11111110 补码:11111111(-1会先被转化成补码赋给无符号的c) 打印时整型提升(无符号位提升,直接补0): 00000000000000000000000011111111(此时这个二进制再计算机内存中,为补码) 打印原码:00000000000000000000000011111111(无符号数原码和补码相等) 打印结果为:255
讲到这里,刚刚的题也给大家解释清楚了,现在想再补充一个内容,以char举例,竟然char的范围是-128~127/0~255,那么用二进制码表示的位置是怎样的呢?这里展示一个图解,能极大的帮助大家理解这个问题。
以上两张图,外圈是存储再计算机中的补码表示形式,内圈是对应类型原码所表示的十进制数值,此图用以表示有符号和无符号对应的数字存储形式,其实不但应用于char类型,同样也适用于int,long,longlong等类型。
这里还要注明一点,char类型中10000000中按照常理来说表示的应该是-0才对,但是再计算机编译环境中的规定,表示的是-128,不只是char,int,long等其他类型也是如此。所以,再有符号类型中,负数最小值的绝对值一般比正数的最大值大1。
下面再来看例题二,一下就简单多了
例题2
#include <stdio.h> int main() { char a = -128; printf("%d\n", a); return 0; }
问:以上代码打印结果?
对于-128,计算机中放入的对应值在上图中已经显而易见,10000000,而%u则表示打印无符号的整型,在-128的整型提升中,10000000,变成了11111111 11111111 11111111 10000000(有符号数整型提升补符号位),在打印无符号数的时候结果为4294967168。
例题3
#include <stdio.h> int main() { char a = 128; printf("%d\n", a); return 0; }
问:以上代码的打印结果?
可以注意到128已经超出了char能存储的范围,但是这个程序硬是将这样一个值放入其中的化,那么128会转化成补码10000000,放入a,那a的补码10000000解读过来又是什么呢?是-128,所以这是和例题二便重合了,打印结果依然是 4294967168。
例题4
#include <stdio.h> #include<string.h> int main() { char a[1000]; for (int i = 0; i < 1000; i++) { a[i] = -1 - i; } printf("%d\n", strlen(a)); return 0; }
问:以上代码的打印结果?
我们之前已经介绍过了char类型数据在内存中的存储,题目代码中,是由-1逐渐存到-1000,那在内存中便是-1,-2,-3。。。-127,-128,127,126,125。。。3,2,1,0,-1,-2。。。
以以上这种方式存储在内存中,并且存储的数字是一个完美的闭环,在打印字符串长度的过程中,我们知道strlen计算的截止标志是'\0',那么strlen最后计算的便是-1,-2,-3。。。-127,-128,127,126,125。。。3,2,1这之间数字的个数了,程序最终运行打印得255。
如果不了解strlen和字符串函数,我的上一篇博客有详细讲到大家可以参考(链接如下):
例题5
#include <stdio.h> unsigned char i = 0; int main() { for (int i = 0; i <= 255; i++) { printf("Hello world!"); } return 0; }
问:以上代码输出结果?
由于unsigned char类型最大值也无法超过255,所以此程序会循环打印Hello world!
例题6
#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\n", ptr1[-1], *ptr2); return 0; }
问:以上代码输出结果?
&a取出整个数组的地址,加一个1跳过一个数组指向末尾,在打印ptr1[-1]时相当于*(ptr1 - 1),所以前一个打印的值是4
第二个指针在初始化的时候转成了整型,加一再解引用相当于往后移了一个字节,最后转成int*类型指针最后打印结果为0x02000000
浮点数在内存中的存储
再以一道题引入
#include <stdio.h> 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; }
问:以上代码输出结果?
先把答案给大家,看看大家是否能理解
n的值为:9 *pFloat的值为:0.000000 num的值为:1091567616 *pFloat的值为:9.000000
相信第二行和第三行的结果会与本来所想的结果不同吧
具体原因还是要来了解一下浮点数再内存中的存储
浮点数在内存中的存储遵循IEEE754标准,是目前最广泛使用的浮点数表示方法
任意一个二进制浮点数V可以表示成下面的形式:
V=(-1)^S*M*2^E
其中:(-1)^S表示符号位,S=0时V为正数;S=1时V为负数
M表示有效数字,M大于等于1,小于2
2 ^E表示指数位
单精度浮点数,也就是float:有一个符号位,8个指数位以及23位尾数位
双精度浮点数,也就是double:有一个符号位,11位指数位以及52位尾数位
拿5.0来举例:
十进制的5.0,写成二进制为101.0,相当于1.01*2^2。
可以得到S=0,M=1.01,E=2。在计算机保存M时,默认第一位总是1,因此可以被舍去,只保留后面的部分。在保存1.01的时候,在计算机中保存的数为01,在读取时,在加上之前省略的1,变回1.01,再将剩下的用0填充。因此M在储存1.01时,便为01000000000000000000000(23位)
再来看E,E作为一个无符号整数,再E为8位时,取值范围是0~255;如果为11位,则取值为0~2047,但是在实际运用的过程中,也常常会碰到指数为负的情况,这怎么办呢?据此,IEEE754规定,存入内存时在指数位E之间加一个中间数,对于八位的E,这个中间数为127;而对于11位的E,这个数为1023,指数位存入的数据便是(八位:127+E)(十一位:1023+E)所以就可以据此来表示指数位的正负了。
最终5.0在计算机中存储的数据为:
0 10000001 01000000000000000000000
从内存中读取浮点数
在读取浮点数的过程中,根据数值E的不同,可以分以下三类情况
E不全为0或一
这样的指数值代表了有效的浮点数。在解析中,指数值会见去中间量,得到最终的指数值。
对语32位float类型浮点数,中间值为127,如刚才存入的E:10000001,最终指数值为(129-128=2) 对于64位的double也是同理。
E全为0
这种情况下,浮点数表示出来的是一种极其小的数值,浮点数小于等于10^-127/10^-1023,这时,返回浮点数时M便不在第一位补1,而是直接还原为0.000.....的小数
E全为1
这时表示浮点数是一个无穷大的数,符号位决定了是正无穷还是负无穷
最后,在来看看之前的那道题
#include <stdio.h> 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; }
9以整型形式存储的过程中,得到的二进制为:
0000 0000 0000 0000 0000 0000 0000 1001
将此数以浮点数形式读出,会发现E全为0,是上方提到的情况,所以小数就为0.000000
而浮点数9.0,为二进制1001.0,转换成存入计算机中的形式1.001*2^3,S=0,E=127+3=130,M为00100000000000000000000,所以二进制表示式为
0 10000010 00100000000000000000000
将此数转化成整型打印出来便是 1091567616了。
博主的话
一篇博客写下来真的耗时耗力,本来想着上午快快肝完,这一写就是将近一整天┭┮﹏┭┮,如果感觉本篇博客对你有帮助的话,还请留个小赞,放个收藏,点个关注再走啊!如果本博客有任何错误和不完善还请各位大佬再评论区指正,那就先到这里了,下篇博客再见。比