# 1. 字节序 (Endianness / 大小端模式)
核心概念:字节序定义了多字节数据类型(例如 int, short, double 等)在内存中是如何存储其字节的顺序。
一个32位的整数 0x01020304 由4个字节组成:0x01, 0x02, 0x03, 0x04。
0x01是最高有效字节 (Most Significant Byte, MSB)0x04是最低有效字节 (Least Significant Byte, LSB)
内存地址总是从低到高增长。根据将 MSB 还是 LSB 存放在低地址,产生了两种模式:
| 模式 | 描述 | 存储示例 (int 0x01020304) |
|---|---|---|
| 大端模式 (Big-Endian) | 最高有效字节 (MSB) 存放在 最低的内存地址。符合人类阅读习惯。 | 01 02 03 04 |
| 小端模式 (Little-Endian) | 最低有效字节 (LSB) 存放在 最低的内存地址。符合计算机处理逻辑。 | 04 03 02 01 |
# a. 大端模式 (Big-Endian)
对于 int num = 0x01020304;,其在内存中的存储方式如下:
内存地址 (低 ---> 高)
+--------+--------+--------+--------+
| 0x01 | 0x02 | 0x03 | 0x04 |
+--------+--------+--------+--------+
0x1000 0x1001 0x1002 0x1003
- 常见架构:PowerPC, SPARC, 网络字节序 (Network Byte Order)。
# b. 小端模式 (Little-Endian)
对于 int num = 0x01020304;,其在内存中的存储方式如下:
内存地址 (低 ---> 高)
+--------+--------+--------+--------+
| 0x04 | 0x03 | 0x02 | 0x01 |
+--------+--------+--------+--------+
0x1000 0x1001 0x1002 0x1003
- 常见架构:x86, x86-64, ARM (大部分模式下)。
# c. 如何判断当前系统的字节序?
可以通过检查一个整数的第一个字节来判断。整数 1 在内存中表示为 0x00000001。
- 在小端系统中,最低地址存放的是
0x01。 - 在大端系统中,最低地址存放的是
0x00。
#include <iostream>
void check_endianness() {
int num = 1;
// 将int指针强制转换为char指针,指向num的最低地址
char* ptr = reinterpret_cast<char*>(&num);
if (*ptr == 1) {
std::cout << "本系统是 小端模式 (Little-Endian)" << std::endl;
} else {
std::cout << "本系统是 大端模式 (Big-Endian)" << std::endl;
}
}
# 2. 内存对齐 (Memory Alignment)
核心概念:CPU访问内存不是逐字节进行的,而是以块(通常是2, 4, 8字节)为单位。为了让CPU高效地读取数据,编译器会自动将数据存放在特定地址,这个地址通常是其数据类型大小的整数倍。
为什么需要对齐?
- 性能:对齐的数据可以被CPU在一个总线周期内读取。如果数据未对齐,CPU可能需要两次读取再拼接数据,降低性能。
- 硬件要求:某些硬件平台(尤其是RISC架构,如ARM)不允许访问未对齐的数据,强行访问会触发硬件异常。
# a. 对齐规则
- 成员对齐:结构体(
struct)或类(class)的每个成员,其存放的起始地址相对于结构体起始地址的偏移量,必须是其自身大小(或指定对齐值)的整数倍。 - 整体对齐:结构体或类的总大小,必须是其最宽的成员(或指定对齐值)大小的整数倍。
示例分析:
#include <iostream>
struct MyStruct {
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
int main() {
// 预期大小: 1 + 4 + 2 = 7
// 实际大小: 12
std::cout << "sizeof(MyStruct) = " << sizeof(MyStruct) << std::endl;
}
内存布局:

char a: 位于偏移量0。int b: 大小为4,其偏移量必须是4的倍数。因此编译器在a后填充3字节,使b从偏移量4开始。short c: 大小为2,b结束后偏移量为8,是2的倍数,可以直接存放。- 整体对齐: 结构体最宽成员是
int b(4字节),总大小必须是4的倍数。当前大小为1 (a) + 3 (pad) + 4 (b) + 2 (c) = 10字节。为满足4的倍数要求,末尾再填充2字节,最终大小为12字节。
# b. 如何控制对齐 #pragma pack(n)
可以使用预处理指令 #pragma pack(n) 来改变编译器的默认对齐系数。n 可以是 1, 2, 4, 8 等。成员对齐时,将按照 n 和成员自身大小中 较小 的值进行对齐。
#pragma pack(1) // 设置对齐系数为1,即无填充
struct MyStructPacked {
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
#pragma pack() // 恢复默认对齐
// sizeof(MyStructPacked) 的结果将是 1 + 4 + 2 = 7
# 3. 常见导致问题的场景
# a. 网络通信 (字节序问题)
不同架构的计算机字节序可能不同。而网络协议规定字节序为大端模式 (Network Byte Order)。
-
问题:小端机器 (x86) 发送
0x01020304(内存中为04 03 02 01),大端服务器接收后会误读为0x04030201。 -
解决方案:发送前,统一将数据从主机字节序 (Host Order) 转换到网络字节序 (Big-Endian);接收后反向转换。
- 常用函数:
htons(),htonl(),ntohs(),ntohl()h代表 host,to代表 to,n代表 network,s代表 short,l代表 long。
#include <arpa/inet.h> // 在Linux/macOS中 // #include <winsock2.h> // 在Windows中 uint32_t num = 0x01020304; uint32_t net_num = htonl(num); // 转换为主机序到网络序 // send(socket, &net_num, sizeof(net_num), 0); - 常用函数:
# b. 文件读写与数据持久化 (字节序与对齐问题)
直接将结构体的二进制内存写入文件,会同时写入字节序和内存对齐产生的填充字节。
MyStruct data;
// file.write(reinterpret_cast<char*>(&data), sizeof(MyStruct)); // 错误做法
- 问题:
- 字节序:在小端机器上写的文件,用大端机器读会出错。
- 对齐:不同编译器或编译选项可能导致填充方式不同,造成跨平台/版本解析失败。
- 解决方案:采用序列化 (Serialization)。逐个成员地进行读写,并为文件格式定义统一的字节序(通常推荐大端)。
# c. 嵌入式开发与硬件交互 (字节序与对齐问题)
直接读写硬件寄存器时,必须严格遵守硬件手册的规定。
- 字节序:硬件寄存器通常是大端模式。小端CPU在写入时必须手动转换字节序。
- 对齐:访问硬件寄存器必须严格对齐。例如,对一个32位(4字节)寄存器,其访问地址必须是4的倍数,否则在很多嵌入式处理器上将导致硬件异常。