
🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
-
- [前景回顾:内存操作函数核心速记 📝](#前景回顾:内存操作函数核心速记 📝)
- [一、整数在内存中的存储:补码为王 🎯](#一、整数在内存中的存储:补码为王 🎯)
- [二、大小端字节序:多字节数据的存储顺序 🔄](#二、大小端字节序:多字节数据的存储顺序 🔄)
-
- [1. 什么是大小端字节序](#1. 什么是大小端字节序)
- [2. 不同环境的字节序使用](#2. 不同环境的字节序使用)
- [3. 笔面试真题:设计程序判断机器字节序](#3. 笔面试真题:设计程序判断机器字节序)
- [4. 经典例题解析:char类型的截断与整型提升](#4. 经典例题解析:char类型的截断与整型提升)
-
- [例题1:signed/unsigned char的输出差异](#例题1:signed/unsigned char的输出差异)
- 例题2:unsigned类型的死循环陷阱
- [5. 综合例题:指针与内存地址的运算](#5. 综合例题:指针与内存地址的运算)
- [三、浮点型在内存中的存储:IEEE 754标准 📐](#三、浮点型在内存中的存储:IEEE 754标准 📐)
-
- [1. 引子:一个反常识的例子](#1. 引子:一个反常识的例子)
- [2. IEEE 754标准的核心规则](#2. IEEE 754标准的核心规则)
- [3. 浮点型的内存布局](#3. 浮点型的内存布局)
- [写在最后 📝](#写在最后 📝)
内存操作函数讲完之后,我们终于要深入C语言的底层核心------数据在内存中的存储规则!这一篇聚焦三大核心知识点:整数的内存存储、大小端字节序的判断,以及浮点型的特殊存储方式,结合大量笔面试真题,帮你彻底搞懂数据存储的底层逻辑!
前景回顾:内存操作函数核心速记 📝
回顾内存操作函数的特性,能帮我们更好理解数据存储的意义:
- 内存函数以字节为单位操作内存,不关心数据类型。
- 不同数据类型(整数/浮点型)在内存中的存储格式完全不同。
- 多字节数据的存储顺序(大小端)会直接影响数据读取结果。
一、整数在内存中的存储:补码为王 🎯
整数在内存中的存储遵循补码规则,这部分内容在《操作符详解》中已经讲解,这里做核心回顾:
- 原码:直接根据数值正负写出的二进制表示。
- 反码:正数反码与原码相同;负数反码是原码符号位不变,其余位按位取反。
- 补码:正数补码与原码相同;负数补码是反码加1。
- ✅ 核心结论 :计算机中整数一律以补码形式存储,原因是补码能把减法运算转化为加法运算,简化硬件设计。
二、大小端字节序:多字节数据的存储顺序 🔄
对于char这种单字节数据,不存在存储顺序问题;但对于int(4字节)、short(2字节)等多字节数据,就会涉及大小端字节序的问题,这是笔面试的高频考点!
1. 什么是大小端字节序
以数据0x11223344(4字节)为例,内存地址有高低之分,数据字节也有高低之分:
- 数据的高低字节 :
0x11是高位字节 ,0x44是低位字节 - 内存的高低地址 :内存地址编号从小到大,依次是低地址→高地址
| 字节序类型 | 存储规则 | 存储示例(低地址→高地址) |
|---|---|---|
| 小端字节序 | 低位字节存低地址,高位字节存高地址 | 44 33 22 11 |
| 大端字节序 | 低位字节存高地址,高位字节存低地址 | 11 22 33 44 |
2. 不同环境的字节序使用
- Intel架构、多数ARM/DSP、VS编译器:小端模式
- KEIL C51(单片机开发):大端模式
- 部分ARM处理器:支持硬件切换大小端模式
3. 笔面试真题:设计程序判断机器字节序
这是百度等大厂的经典笔试题,核心思路是利用int和char的类型差异,只读取数据的低地址第一个字节。
方案1:直接判断
c
#include <stdio.h>
int main()
{
int a = 1; // 补码:0x00000001
// 强转为char*,只取低地址第一个字节
if (*(char*)&a == 1)
printf("小端\n"); // 小端存储:低地址是01
else
printf("大端\n"); // 大端存储:低地址是00
return 0;
}
方案2:封装成函数(更规范)
c
#include <stdio.h>
// 返回1:小端;返回0:大端
int check_sys()
{
int a = 1;
return *(char*)&a == 1 ? 1 : 0;
}
int main()
{
if (check_sys() == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
4. 经典例题解析:char类型的截断与整型提升
char类型占1字节(8位),存储数据时会发生截断 ,读取时会发生整型提升,结合大小端和有无符号属性,会产生很多看似反常识的结果。
例题1:signed/unsigned char的输出差异
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;
}
分析过程:
-1的补码:11111111 11111111 11111111 11111111- 截断(存到char):
a=b=c=11111111 - 整型提升:
a/b(signed char):补符号位→全1,原码是-1c(unsigned char):补0→00000000 00000000 00000000 11111111,值为255
输出结果 :a=-1 b=-1 c=255
例题2:unsigned类型的死循环陷阱
c
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
分析过程:
unsigned char的取值范围是0~255- 当
i=255时,执行i++会溢出,值变为0 - 循环条件
i<=255恒成立,导致死循环
💡 结论:使用
unsigned类型时,要特别注意循环边界条件,避免溢出导致的逻辑错误。
5. 综合例题:指针与内存地址的运算
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\n", ptr1[-1], *ptr2);
return 0;
}
内存分析(小端环境) :
数组a的内存布局(低→高):01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
&a+1:跳过整个数组,ptr1指向数组末尾后一位;ptr1[-1]等价于*(ptr1-1),指向最后一个元素4,输出4。(int)a+1:a是数组首地址,强转为int后+1,指向第一个元素的第二个字节;*ptr2读取4字节为00 00 00 02,输出2000000。
输出结果 :4 2000000
三、浮点型在内存中的存储:IEEE 754标准 📐
浮点型(float/double)的存储方式和整数完全不同,遵循IEEE 754国际标准,这也是C语言的一个难点。
1. 引子:一个反常识的例子
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.0;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}
运行输出:
n的值为:9
*pFloat的值为:0.000000
n的值为:1091567616
*pFloat的值为:9.000000
结论 :同一个内存数据,按整数解析和按浮点型解析,结果完全不同!
2. IEEE 754标准的核心规则
任意二进制浮点数 V V V都可以表示为:
V = ( − 1 ) S × M × 2 E V = (-1)^S \times M \times 2^E V=(−1)S×M×2E
- ( − 1 ) S (-1)^S (−1)S:符号位 , S = 0 S=0 S=0表示正数, S = 1 S=1 S=1表示负数
- M M M:有效数字 ,范围是 1 ≤ M < 2 1 \leq M < 2 1≤M<2
- 2 E 2^E 2E:指数位,表示数据的量级
示例:浮点数5.5的表示
- 十进制
5.5→ 二进制101.1 - 科学计数法: 1.011 × 2 2 1.011 \times 2^2 1.011×22
- 对应参数: S = 0 S=0 S=0, M = 1.011 M=1.011 M=1.011, E = 2 E=2 E=2
3. 浮点型的内存布局
IEEE 754标准规定了float和double的内存结构:
| 类型 | 总字节数 | 符号位 S S S | 指数位 E E E | 尾数位 M M M |
|---|---|---|---|---|
| float | 4(32位) | 1位 | 8位 | 23位 |
| double | 8(64位) | 1位 | 11位 | 52位 |
两个关键优化点
-
有效数字 M M M的优化 :
因为 1 ≤ M < 2 1 \leq M < 2 1≤M<2,所以 M M M的整数部分一定是
1,存储时可以省略这个1,只存小数部分。比如 M = 1.011 M=1.011 M=1.011,内存中只存011,读取时再补回1,节省1位空间。 -
指数位 E E E的优化 :
E E E是无符号整数,但实际可以是负数。存储时 E E E需要加上一个中间值:float(8位 E E E):中间值=127double(11位 E E E):中间值=1023
示例:浮点数9.0的存储
- 十进制
9.0→ 二进制1001.0→ 1.001 × 2 3 1.001 \times 2^3 1.001×23 - S = 0 S=0 S=0, M = 1.001 M=1.001 M=1.001, E = 3 E=3 E=3
- 存储 E E E: 3 + 127 = 130 3+127=130 3+127=130 → 二进制
10000010 - 存储 M M M:省略
1,只存001,后面补0至23位 - 最终内存结构:
0 10000010 00100000000000000000000 - 转为十进制整数:
1091567616(对应引子题的输出结果)
写在最后 📝
数据在内存中的存储是C语言的底层核心知识点,也是笔面试的"拦路虎"。想要吃透这部分内容,关键在于三点:
- 整数存储牢记补码规则 ,char类型注意截断与整型提升;
- 多字节数据要分清大小端字节序,掌握判断方法;
- 浮点型存储遵循IEEE 754标准,理解符号位、指数位、尾数位的含义。
这些知识点看似抽象,但结合具体例题分析后,就能发现其中的规律。建议大家把文中的例题都手动敲一遍,观察运行结果,加深对底层逻辑的理解。至此,C语言内存相关的核心内容就全部讲解完毕啦!