引言很多人写C语言天天写int a; float b;但很少思考一个问题我们写的数字在计算机内存这一大片字节格子里到底是怎么存放的 为什么同样一串二进制当int读是9当float读就变成0.000000为什么char a-1打印%u会出来255大小端又是什么鬼今天咱们抛开晦涩课本把内存存储一次性讲明白。一、整数在内存存的不是原码是补码我们都知道计算机只认识0和1。 对于有符号整数有三套二进制表示原码、反码、补码。原码直接把数字转成二进制最高位当做符号位0代表正数1代表负数。反码正数反码等于原码负数符号位不动其余全部按位取反。补码正数补码原码负数 反码 1。重点内存里面整数一律存放补码不是原码为啥非要搞补码直接存原码不好吗CPU只有加法器没有减法器。减法可以转换成补码的加法运算。计算5‑3等价于5 (-3)的补码相加。解决原码里面存在0和‑0两个编码的问题补码体系中0只有唯一一份编码还多挤出来一个负数可用。举个8位signed char例子-1原码1000 0001-1反码1111 1110-1补码1111 1111所以内存中存的就是11111111。 这就解释了经典笔试题char a -1; unsigned char c -1; printf(%d, c); //输出255内存字节是0xff当成无符号解读就是255。8位有符号char范围‑128 ~ 127注意‑128没有对应的原码和反码是补码独有的二进制10000000。无符号数溢出环形绕圈现象无符号数字到最大值再加1不会报错直接绕回0。unsigned char范围0‑2552551直接变成0。 之前那道高频面试题char a[1000]; int i; for(i 0; i 1000; i) { a[i] -1 - i; } printf(%d, strlen(a));strlen统计到\0字节0停止。‑1‑i赋值给char截断保留低8位。当i255‑1‑255 ‑256低8位全部为0也就是\0。前面一共255个有效字符输出255。小技巧8位数据只要是256的整数倍截断低8位一定等于0。二、大小端同一个数字两种摆放方式问题来了一个int占4字节4个字节怎么排列放在内存地址上这里就诞生了大端、小端模式。举个例子int a 0x11223344;把这个int拆开成4个字节 高字节0x110x22低字节0x330x44内存地址是从小到大增长的就像一排从左向右编号的盒子。小端模式我们电脑VS、X86/X64都是小端低字节放到低地址左边盒子高字节放到高地址右边盒子内存排布地址由低→高44 33 22 11VS内存窗口从左往右就是地址由低到高看到的就是这个顺序。大端模式单片机KEIL C51常用高字节放到低地址低字节放到高地址内存排布地址由低→高11 22 33 44阅读更符合人类阅读习惯。注意大小端只是字节的摆放顺序不会改变数值只是内存存储布局不同。面试手写判断本机是大端还是小端思路把int1拿第一个内存字节看值。 int i1小端下最低地址存的字节是01大端最低地址存00。写法1指针强制转换版int check_sys() { int i 1; return *(char *)i; }返回1代表小端返回0代表大端。写法2联合体版本利用联合体成员共享同一起始内存int check_sys() { union{ int i; char c; }un; un.i 1; return un.c; }联合体所有成员共用同一块内存起始位置un.c直接取第一个字节不需要强制转换。三、浮点数float/double存储规则完全不一样整数用补码存但是浮点数完全不走补码这套体系遵循国际标准IEEE754。公式 $$V(-1)^S × M × 2^E$$拆解三部分S符号位1bit0正数1负数E指数位用来存放2的多少次方M有效数字/尾数类型S符号位E指数位M有效位float(32位单精度)1 bit8 bit23 bitdouble(64位双精度)1 bit1 bit11 bit举例子5.0二进制写成101.0科学计数法$1.01 × 2^2$ S0M1.01E2。两个关键规则M的隐藏1M规定范围 $1 ≤ M 2$一定是1.xxxx。 标准直接把整数部分的1舍弃不存内存只保存小数点后面xxxx部分。读取的时候自动把1加回来凭空多出来1位精度。E指数要加偏移量中间数E是无符号数不能存负数。floatE真实值 127存入内存doubleE真实值 1023存入内存比如E真实等于2float存入内存写2127 129。E的三种读取情况E不全0不全1正常情况真实指数 内存E − 偏移量(127/1023)M补上隐藏的1。E全部为0隐藏的1不再加上M变成0.xxxx。用来表示接近0的极小数字±0。E全部为1如果M全0代表正负无穷M不为0代表NaN无效数值。经典迷惑代码看完再也不懵int n 9; float *pFloat (float *)n; printf(n的值为:%d\n, n); printf(*pFloat的值为:%f\n, *pFloat); *pFloat 9.0; printf(n的值为:%d\n, n); printf(*pFloat的值为:%f\n, *pFloat);输出n的值为:9 *pFloat的值为:0.000000 n的值为:1091567616 *pFloat的值为:9.000000n9内存二进制00000000 00000000 00000000 00001001。把这串二进制强行按float规则解读。 S0E全0进入E全0特殊场景算出来是极小接近0的数字打印就是0.000000。往这个内存地址写入浮点数9.0按照IEEE754编码写入一串bit。再用int去读取这串bit就得到十进制数字1091567616。⚠️重点提醒int和float虽然都是4字节编码体系完全两套同样二进制解读方式不同数值天差地别。同时注意部分十进制小数例如1.2二进制无法精确表达float/double存储会存在微小精度误差。本篇核心总结整数内存存补码CPU靠补码把减法变成加法运算有符号溢出属于未定义行为无符号溢出环形回绕。大小端是字节排列顺序小端低字节放低地址电脑X86都是小端两种手写检测方式指针强转、联合体。浮点数不使用补码遵循IEEE754S符号位E带偏移的指数M有隐藏最高位1分E全0、全1、普通三种场景。同样的内存二进制序列用int读、用float读得到完全不同结果因为两套解码规则。很多C语言坑根源就是不懂内存存储。面试经常考的溢出题、大小端、int和float强制类型转换迷惑题底层全部来源于这些知识点。如果你喜欢本文欢迎点赞收藏后面继续更新C指针、结构体内存对齐的底层内容。有看不懂的地方欢迎评论区留言讨论886~。