文章目录
- 前言
- 1. 整数在内存中的存储
- 2. 大小端字节序和字节序判断
-
- 2.1 什么是大小端?
- 2.2 为什么有大小端?
- 2.3 练习
-
- 2.3.1 练习1
- 2.3.2 练习2
-
- 最关键的坑:printf 与 %d(整型提升)
- 为什么 b (signed char) 打印出 -1?
- 为什么 c (unsigned char) 打印出 255?
- 为什么 a (char) 是不确定的?
- 2.3.3 练习3
-
- 第一步:8位内存存储
- 第二步:整型提升与格式说明符
- 第三步:数学计算(算出结果)
- 核心要点总结
- 2.3.4 练习4
-
- 第一步:变量赋值(溢出)
- 第二步:整型提升与符号扩展(“大变身”)
- 第三步:%u 无符号打印
- 核心要点总结
- 2.3.5 练习5
-
- 1. 核心前提:char 的取值范围
- 2. for 循环与二进制溢出(回绕)
- 3. strlen 的工作原理
- 4. 最终结果
- 5. 深入理解:为什么是255而不是256?
- 6. 扩展思考:如果数组更大呢?
- 7. 实际应用中的注意事项
- 8. 总结要点
- 2.3.6 练习6
- 2.3.7 练习7
-
- 1. 环境前提:小端字节序 & X86环境
- 2. 第一个输出:ptr1[-1]
- 3. 第二个输出(难点):*ptr2
- 4. 图解 *ptr2 到底读了什么
- 5. 最终数字的由来
- 3. 浮点数的存储
-
- 3.1 浮点数存储
- 3.2 IEEE 754 对有效数字 M 和指数 E 的特殊规定
- 3.3 浮点数的三种存储情况详解
-
- 🚨 1. 浮点数在内存中的根本结构(以 32 位 float 为例)
- ⚙️ 情况一:规格化数(最常见的数字)
- ⚙️ 情况二:非规格化数(接近 0 的小数)
- ⚙️ 情况三:特殊值(无穷大和 NaN)
- 总结对比表
- 3.4 题目详解
-
- 示例代码
- 第一部分:写入整数,按浮点数读取
-
- 1. 整数 9 在内存中的存储
- 2. 强制类型转换
- 3. 按浮点数规则解读
- 第二部分:写入浮点数,按整数读取
-
- 1. 写入浮点数 9.0
- 2. 按整数规则读取
- 3. 验证浮点数读取
- 内存变化示意图
- 核心要点总结
- 扩展思考
前言
在C/C++编程中,理解数据在内存中的存储方式是深入掌握底层原理的关键。无论是简单的整数运算,还是复杂的浮点数处理,最终都需要转化为二进制形式在计算机内存中进行存储和操作。然而,许多开发者在面对类型转换、字节序、整型提升、浮点数精度等问题时,常常感到困惑,甚至写出存在潜在风险的代码。
本文旨在系统性地解析C/C++中数据在内存中的存储机制,从最基础的整数原码、反码、补码表示,到复杂的大小端字节序,再到浮点数的IEEE 754标准存储格式。通过大量实际代码示例和逐步分析,我们将深入探讨:
无论你是正在学习C/C++的初学者,还是希望深入理解计算机底层原理的中级开发者,本文都将为你提供清晰、系统的知识体系。通过阅读本文,你将能够:
- 准确理解数据在内存中的二进制表示
- 掌握类型转换和整型提升的底层机制
- 熟练分析涉及字节序和内存对齐的问题
- 深入理解浮点数精度问题和特殊值的处理
- 编写更加健壮、可移植的底层代码
让我们开始这段探索内存存储奥秘的旅程吧!
1. 整数在内存中的存储
在讲解操作符的时候,我们就讲过了下面的内容:
整数的二进制表示方法有三种,即:原码、反码和补码。
有符号的整数,三种表示方法均有符号位和数值位两部分。符号位都是用 0 表示“正”,用 1 表示“负”,最高位的一位是被当做符号位,剩余的都是数值位。
- 正整数的原、反、补码都相同。
- 负整数的三种表示方法各不相同。
- 原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
- 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
- 补码:反码 + 1 就得到补码。
对于整型来说:数据存放在内存中其实存放的是二进制的补码。
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于:
2. 大小端字节序和字节序判断
当我们了解了整数在内存中存储后,我们调试看⼀个细节:

2.1 什么是大小端?
其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储,下面是具体的概念:
- 大端(存储)模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。
- 小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。
上述概念需要记住,方便分辨大小端。
2.2 为什么有大小端?
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit 位。但是在 C 语言中除了 8 bit 的 char 之外,还有 16 bit 的 short 型,32 bit 的 long 型(要看具体的编译器)。另外,对于位数大于 8 位的处理器,例如 16 位或者 32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16 bit 的 short 型变量 x,在内存中的地址为 0x0010,x 的值为 0x1122,那么 0x11 为高字节,0x22 为低字节。
- 对于大端模式,就将 0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。
- 小端模式,刚好相反。
我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的 ARM、DSP 都为小端模式。有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式。
2.3 练习
2.3.1 练习1
请简述⼤端字节序和⼩端字节序的概念,设计⼀个⼩程序来判断当前机器的字节序。(10分)–百度笔试题
代码展示
int main()
{
int a = 1;
if (*(char*)&a == 1)
{
printf(\”小端\\n\”);
}
else
{
printf(\”大端\\n\”);
}
return 0;
}
//代码2
int check_sys()
{
int a = 1;
if (*(char*)&a == 1)
return 1; //小端
else
return 0; //大端
}
//代码3
int check_sys()
{
int a = 1;
return (*(char*)&a);
}
int main()
{
if (check_sys() == 1)
{
printf(\”小端\\n\”);
}
else
{
printf(\”大端\\n\”);
}
return 0;
}
2.3.2 练习2
思考一下为什么会出新这种结果
详细讲解
这段代码定义了三个字符类型的变量,并赋了同样的值 -1:
signed char b = –1; // b 在内存中是:11111111 (8位,代表 -1)
unsigned char c = –1; // c 在内存中也是:11111111 (但代表 255)
char a = –1; // a 在内存中是:11111111 (但代表什么取决于编译器)
在计算机内存中,整数是以补码形式存储的。对于 8 位(1字节)的 char 类型,-1 的二进制补码是 11111111。
注意:标准 C/C++ 中,默认的 char 到底是有符号(signed char)还是无符号(unsigned char)是由编译器决定的。所以变量 a 的值存在不确定性。
最关键的坑:printf 与 %d(整型提升)
代码中使用了:
printf(\”a = %d, b = %d, c = %d\\n\”


