编译器优化诱发的 BUG

有如下枚举类型:

1
2
3
4
5
6
7
8
typedef enum
{
A = 0X0001,
B = 0X0002,
// ...
AA = 0X0011,
BB = 0X0012
} TEST_ENUM;

如果在枚举末尾不添加一个类似 AAAA = 0X1111 这样需要占用 short 类型(即 16 位)的成员,编译器可能会将这个枚举类型优化为 char 类型(8 位)。

但开发者原本的意图显然是 short 类型——因为初始化值写的是 0X0001,而不是 0X01。一旦编译器将其优化为 char,后续再按照 short 类型去使用,就会产生难以察觉的 BUG

如何解决?

很简单:在枚举末尾添加一个高 8 位不为 0 的值即可,例如 AAAA = 0X1111
有了这个成员,编译器就不会再将枚举类型优化为 char,而是保留为 short

会引发什么样的 BUG

例 1:跨枚举类型传参导致数据截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef enum
{
A = 0X0001,
B = 0X0002,
C = 0X0003,
// ...
O = 0X000F
} TEST_ENUM1; // 编译器会将其优化为 char 类型

typedef enum
{
AA = 0X1111,
BB = 0X2222
} TEST_ENUM2; // 不会被优化,仍为 short 类型

假设有一个函数,其形参类型为 TEST_ENUM1,函数内部用一个 short 类型变量接收该参数。起初,传入的参数都来自 TEST_ENUM1,一切正常。

但当你认为 TEST_ENUM1TEST_ENUM2 本质上相同,于是将 TEST_ENUM2 中的枚举值作为实参传入该函数时,BUG 就出现了:

TEST_ENUM2 的枚举值会被强制截断为 char 类型,只保留低 8 位,高 8 位数据被丢弃。
如果这个值要通过通信发送给从设备,而设备期望完整的 16 位数据,那么通信就会失败,得不到预期的结果。

例 2:结构体大小与预期不符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef enum
{
A = 0X0001,
B = 0X0002,
C = 0X0003,
// ...
O = 0X000F
} TEST_ENUM3; // 编译器会将其优化为 char 类型

typedef struct
{
TEST_ENUM3 E1;
unsigned char value1;
unsigned short value2;
} TEST_STRUCT;

使用 sizeof 关键字(注意:sizeof 是关键字,也是单目运算符,但并非函数)获取 TEST_STRUCT 的大小时,得到的结果很可能与你手动计算的不一致。
如果你确定自己的计算没有错误,那么大概率就是因为编译器悄悄优化了枚举的大小,导致结构体的内存布局发生了变化。

总结:当枚举中所有成员的值都能用 char 表示时,编译器为了节省空间,可能会将其底层类型优化为 char。如果业务逻辑依赖枚举为 16 位或更大,记得在枚举末尾显式添加一个“大数”成员,强制编译器保持其大小。这类隐式优化极易引发隐蔽的跨模块、跨数据类型的 BUG,务必警惕。