第1章:数据类型与字节对齐——跨平台的第一道坎

说实话,我刚开始做跨平台开发时,最头疼的就是数据类型。你以为int就是4个字节?太天真了。我在嵌入式平台和PC之间传数据,结果结构体对不上,程序直接崩溃。嗯,今天我们就来聊聊这个坑。

1.1 基本数据类型的大小——sizeof陷阱

先看个简单的例子。你在Windows上写代码,int是4字节。换到某些嵌入式平台,int可能变成2字节。为什么会这样?因为C语言标准只规定了最小范围,没规定具体大小。

// 你以为的:
printf("int size: %zu\n", sizeof(int));  // 4

// 实际在不同平台:
// Windows/Linux x86_64: 4
// 某些嵌入式MCU: 2
// DSP平台: 有时是4,有时是2

我个人习惯,写跨平台代码时,永远不用int、long这种模糊类型。用里的固定宽度类型,比如int32_t、uint16_t。这样不管换到什么平台,大小都是确定的。

核心原则:跨平台代码中,永远使用定义的固定宽度类型。

类型 大小(字节) 跨平台建议
char 1 可用,但注意signed/unsigned
int 2或4 ❌ 避免使用
long 4或8 ❌ 避免使用
int32_t 4 ✅ 推荐使用
uint64_t 8 ✅ 推荐使用

我在项目中遇到过一个问题:用long存时间戳,在32位平台是4字节,到64位平台变成8字节。结果序列化后的数据,两边解析完全对不上。从那以后,我所有跨平台数据交换,一律用int64_t。

1.2 字节对齐——结构体的隐藏尺寸

你想想看,一个结构体里定义了3个字段,sizeof算出来却比字段大小之和还大。这就是字节对齐在搞鬼。

struct Example {
    char a;      // 1字节
    int b;       // 4字节
    short c;     // 2字节
};
// 你以为:1 + 4 + 2 = 7字节
// 实际:12字节(对齐后)

为什么会这样?因为CPU读取内存时,按字长对齐效率最高。编译器会在字段之间插入填充字节。默认对齐规则是:每个字段的起始地址,必须是其大小的整数倍。

注意:不同平台的对齐规则可能不同。ARM平台和x86平台的对齐策略就有差异。我曾经在ARM上调试一个网络协议栈,结构体大小和PC端差了4个字节,排查了整整一天。

1.3 #pragma pack——手动控制对齐

当你需要精确控制结构体大小时,可以用#pragma pack。比如你要和硬件寄存器交互,或者做网络协议解析,对齐必须和对方一致。

#pragma pack(push, 1)  // 按1字节对齐
struct PackedExample {
    char a;
    int b;
    short c;
};
#pragma pack(pop)

// 现在sizeof(PackedExample) = 7
// 没有填充字节,紧凑排列

我建议,使用#pragma pack时要注意几点:

  • 只在需要精确控制的地方使用,不要全局设置
  • 用push/pop成对使用,避免影响其他代码
  • 对齐后的结构体访问速度会变慢,这是性能换空间

小技巧:如果你不确定当前平台的对齐方式,可以用offsetof宏来检查每个字段的偏移量。

1.4 跨平台结构体序列化——实战避坑

序列化是跨平台通信的核心。说白了,就是把内存里的结构体,变成一串字节流,让对方能正确解析。这里坑特别多。

我曾经写过一个网络通信模块,结构体直接memcpy发送。结果在大小端不同的平台之间,整数字节序反了,解析出来全是乱码。嗯,从那以后我学乖了。

// 安全的序列化方式
void serialize_int32(uint8_t* buf, int32_t value) {
    buf[0] = (value >> 24) & 0xFF;  // 大端序
    buf[1] = (value >> 16) & 0xFF;
    buf[2] = (value >> 8) & 0xFF;
    buf[3] = value & 0xFF;
}

int32_t deserialize_int32(const uint8_t* buf) {
    return ((int32_t)buf[0] << 24) |
           ((int32_t)buf[1] << 16) |
           ((int32_t)buf[2] << 8)  |
           buf[3];
}

跨平台序列化的核心原则:

  1. 固定大小:所有字段用int32_t、uint64_t等固定宽度类型
  2. 固定字节序:统一用大端序(网络字节序)传输
  3. 固定对齐:序列化时按1字节打包,不要依赖编译器对齐
  4. 版本控制:结构体头部加版本号,方便后续扩展

避坑指南:我曾经在升级协议时,忘了加版本号。新老设备之间数据解析全乱套。后来所有结构体头部都加一个magic number和版本字段,再也没出过问题。

知识体系总览

下面这张图,帮你理清本章的核心逻辑:

数据类型与字节对齐知识体系 基本数据类型 字节对齐 跨平台序列化 sizeof陷阱 固定宽度类型 stdint.h使用 默认对齐规则 #pragma pack 性能与空间权衡 字节序处理 版本控制 安全序列化 核心目标:一次编写,到处正确运行

总结一下:数据类型和字节对齐,是跨平台开发的第一道坎。记住三个要点——用固定宽度类型、理解对齐规则、序列化时统一字节序。做到这三点,大部分跨平台问题都能避免。

可可精品资料库