enum类型定义里的reserved
RTX5文档里有这么一段代码
1 enum osStatus_t { 2 osOK = 0, 3 osError = -1, 4 osErrorTimeout = -2, 5 osErrorResource = -3, 6 osErrorParameter = -4, 7 osErrorNoMemory = -5, 8 osErrorISR = -6, 9 osStatusReserved = 0x7FFFFFFF 10 }
其中对Reserved = 0x7FFFFFFF的解释是这样的:

最初没太理解这玩意有什么影响,enum定义的类型,优化了就优化呗,在32位单片机中uint8_t 能表示的东西,用uint32_t表示的话,优势是加快了处理速度,劣势自然就是占用了更多的RAM空间,如果我程序对速度不敏感,对资源敏感,那么对枚举类型的size做优化不是很合理吗?后面才反应过来,这东西是为了确保人家在程序里正常调用库而写的(当然也有连接前生成.o时用的编译器option不一样的情况,本质上也可以直接当成库去理解了)。
要想搞明白这个Reserved,首先得先说一下enum down-size optimization是什么个概念,这个在ARM Compiler V6.16中对应-fshort-enums, -fno-short-enums这两个option,其作用说直白点就是是否允许用覆盖枚举类型的最小参数类型来作为该枚举类型大小,具体出处在《Arm Compiler Reference Guide Version 6.16》文档中。

有了这个选项,如果代码里不写reserved = 0x7FFFFFFF,那么在包含库的APP工程中会发生什么呢?在不考虑将一个不存在定义里的整型强转成对应枚举的情况下(这种操作,懒得讨论了,就跟要将666强行赋值给一个uint8_t一样,神仙难挡),先来理论分析下:
第一步,把维度确认下:
1.维度一:是否优化,库和APP无非就2 * 2 = 4种情况;
2.维度二:库和APP的交互方式,无非就是传参与返回、变量与指针的组合,所以也是4种情况:a.传入变量 b.传入指针 c.返回变量 d.返回指针。
第二步,根据各维度展开理论分析,填表
1&4.针对库和APP同时优化或同时不优化的两种情况:因为编译器对优化的原则是一致的,所以可以确保库和APP里枚举的size是一样的,那这样种直接可以理解为是uintx_t了,不存在任何风险,所以两者下的4种交互方式均为无风险。2.针对库不优化,APP优化的情况,不妨设用到枚举范围是[-128,127],则Lib_sizeof(enum_Test_t) = 4,APP_sizeof(enum_Test_t) = 1。
1)对于a传入变量的情景,例如传入的是一个-1,对应0xFF,而Lib以4字节获取,将会是0x000000FF,会以为是255,数值发生错误,gg
2)对于b传入指针的情景,这将会发生两种情况 ①如果当前内核需要对齐访问,传入的指针并非4字节对齐,直接gg;②至于其余情况,Lib去取4字节范围的数据,跟1字节的目标范围出错还是gg;
3)对于c返回变量的情景,由于函数返回是通过R0寄存器返回,所以无论是负数还是非负数,APP都能取到与目标数据一致的低字节数据,所以这个交互无风险
4)对于d返回指针的情景,这个相当于用uint8_t*的方式去识别uint32_t*的地址,对齐是不会有问题的,至于是否能取得到目标数据,那就得分类讨论了,①小端系统,低地址放低字节,取低地址正好取到目标数据,nice;②大端系统,低地址放高字节,那取到的必然就是0xFF或者0x00了,gg
3.针对库优化,APP不优化的情况,不妨设用到枚举范围是[-128,127],则Lib_sizeof(enum_Test_t) = 1,APP_sizeof(enum_Test_t) = 4。
1)对于a传入变量的情景,跟2的3)类似,由于传参也是依赖寄存器,无论是负数还是非负数,Lib都能取到与目标数据一致的低字节数据,所以这点无风险
2)对于b传入指针的情景,跟2的4)类似,①小端系统,没问题;②大端系统gg
3)对于c返回变量的情景,跟2的1)类似,如果返回的是-1,即0xFF,那APP以4字节获取,将会取到0x000000FF,会以为是255,gg
4)对于d返回指针的情景,跟2的2)类似,①如果当前内核需要对齐访问,返回的指针并非4字节对齐,直接gg②至于其余情况,APP去取4字节的数据,跟1字节的目标范围出错,gg;
| 序号 | 库优化 | APP优化 | 风险 | |||
| 传入变量 | 传入指针 | 返回变量 | 返回指针 | |||
| 1 | 否 | 否 | 无 | 无 | 无 | 无 |
| 2 | 否 | 是 | 有 | 有 | 无 | 有 |
| 3 | 是 | 否 | 无 | 有 | 有 | 有 |
| 4 | 是 | 是 | 无 | 无 | 无 | 无 |
第三步,自然就是实践检验理论了
1 #ifndef __ENUMERATE_H 2 #define __ENUMERATE_H 3 4 #include <stdint.h> 5 6 typedef enum 7 { 8 Enum_Test_N1 = -1, 9 Enum_Test_0 = 0, 10 Enum_Test_P1 = 1, 11 Enum_Test_P2 = 2, 12 } Enum_Test_t; 13 14 15 extern uint32_t Enum_Test_Get_Size(void); 16 extern Enum_Test_t Enum_Test_Get_Enum(void); 17 extern Enum_Test_t* Enum_Test_Get_Enum_Pointer(void); 18 extern int32_t Enum_Test_Set_Enum(Enum_Test_t enumInstance); 19 extern int32_t Enum_Test_Set_EnumPointer(Enum_Test_t* enumPointer); 20 21 #endif //__ENUMERATE_H
1 #include "Enumerate.h" 2 3 uint32_t Enum_Test_Get_Size(void) 4 { 5 return sizeof(Enum_Test_t); 6 } 7 8 uint32_t Status; 9 Enum_Test_t Enum_Test_Get_Enum(void) 10 { 11 Enum_Test_t res; 12 if(0 == Status) 13 { 14 res = Enum_Test_N1; 15 } 16 else if(1 == Status) 17 { 18 res = Enum_Test_0; 19 } 20 else if(2 == Status) 21 { 22 res = Enum_Test_P1; 23 } 24 else 25 { 26 res = Enum_Test_P2; 27 } 28 Status++; 29 Status &= 0x03; 30 31 return res; 32 } 33 34 uint32_t PointerStatus; 35 Enum_Test_t Enum_Test_Buffer[4] = {Enum_Test_N1, Enum_Test_0, Enum_Test_P1, Enum_Test_P2}; 36 Enum_Test_t* Enum_Test_Get_Enum_Pointer(void) 37 { 38 Enum_Test_t* res = (void*)0; 39 40 if(0 == PointerStatus) 41 { 42 res = &Enum_Test_Buffer[0]; 43 } 44 else if(1 == PointerStatus) 45 { 46 res = &Enum_Test_Buffer[1]; 47 } 48 else if(2 == PointerStatus) 49 { 50 res = &Enum_Test_Buffer[2]; 51 } 52 else 53 { 54 res = &Enum_Test_Buffer[3]; 55 } 56 57 PointerStatus++; 58 PointerStatus &= 0x03; 59 60 return res; 61 } 62 63 int32_t Enum_Test_Set_Enum(Enum_Test_t enumInstance) 64 { 65 return enumInstance; 66 } 67 68 int32_t Enum_Test_Set_EnumPointer(Enum_Test_t* enumPointer) 69 { 70 return (int32_t) * enumPointer; 71 }
1 db_printf("Enum_Test_Get_Size() vs sizeof(sizeof(Enum_Test_t)) : %d vs %d\n", Enum_Test_Get_Size(), sizeof(sizeof(Enum_Test_t))); 2 3 4 db_printf("\n\nInput var test\n"); 5 uint32_t tmpSetInstance0 = Enum_Test_Set_Enum(Enum_Test_N1); 6 uint32_t tmpSetInstance1 = Enum_Test_Set_Enum(Enum_Test_0); 7 uint32_t tmpSetInstance2 = Enum_Test_Set_Enum(Enum_Test_P1); 8 uint32_t tmpSetInstance3 = Enum_Test_Set_Enum(Enum_Test_P2); 9 db_printf("Enum_Test_Set_Enum(Enum_Test_N1) = %d\n", tmpSetInstance0); 10 db_printf("Enum_Test_Set_Enum(Enum_Test_0) = %d\n", tmpSetInstance1); 11 db_printf("Enum_Test_Set_Enum(Enum_Test_P1) = %d\n", tmpSetInstance2); 12 db_printf("Enum_Test_Set_Enum(Enum_Test_P2) = %d\n", tmpSetInstance3); 13 14 15 db_printf("\n\nInput ptr test\n"); 16 Enum_Test_t tmpSetPointerIn0 = Enum_Test_N1; 17 Enum_Test_t tmpSetPointerIn1 = Enum_Test_0; 18 Enum_Test_t tmpSetPointerIn2 = Enum_Test_P1; 19 Enum_Test_t tmpSetPointerIn3 = Enum_Test_P2; 20 uint32_t tmpSetPointer0 = Enum_Test_Set_EnumPointer(&tmpSetPointerIn0); 21 uint32_t tmpSetPointer1 = Enum_Test_Set_EnumPointer(&tmpSetPointerIn1); 22 uint32_t tmpSetPointer2 = Enum_Test_Set_EnumPointer(&tmpSetPointerIn2); 23 uint32_t tmpSetPointer3 = Enum_Test_Set_EnumPointer(&tmpSetPointerIn3); 24 db_printf("Enum_Test_Set_EnumPointer(&tmpSetPointerIn0(%d)) = %d\n", Enum_Test_N1, tmpSetPointer0); 25 db_printf("Enum_Test_Set_EnumPointer(&tmpSetPointerIn1(%d)) = %d\n", Enum_Test_0, tmpSetPointer1); 26 db_printf("Enum_Test_Set_EnumPointer(&tmpSetPointerIn2(%d)) = %d\n", Enum_Test_P1, tmpSetPointer2); 27 db_printf("Enum_Test_Set_EnumPointer(&tmpSetPointerIn3(%d)) = %d\n", Enum_Test_P2, tmpSetPointer3); 28 29 30 db_printf("\n\nOutput var test\n"); 31 volatile Enum_Test_t tmpGetInstance0 = Enum_Test_Get_Enum(); 32 volatile Enum_Test_t tmpGetInstance1 = Enum_Test_Get_Enum(); 33 volatile Enum_Test_t tmpGetInstance2 = Enum_Test_Get_Enum(); 34 volatile Enum_Test_t tmpGetInstance3 = Enum_Test_Get_Enum(); 35 db_printf("Enum_Test_Get_Enum() = %d\n", tmpGetInstance0); 36 db_printf("Enum_Test_Get_Enum() = %d\n", tmpGetInstance1); 37 db_printf("Enum_Test_Get_Enum() = %d\n", tmpGetInstance2); 38 db_printf("Enum_Test_Get_Enum() = %d\n", tmpGetInstance3); 39 40 41 db_printf("\n\nOutput var test\n"); 42 Enum_Test_t* tmpGetPointerOut0 = Enum_Test_Get_Enum_Pointer(); 43 Enum_Test_t* tmpGetPointerOut1 = Enum_Test_Get_Enum_Pointer(); 44 Enum_Test_t* tmpGetPointerOut2 = Enum_Test_Get_Enum_Pointer(); 45 Enum_Test_t* tmpGetPointerOut3 = Enum_Test_Get_Enum_Pointer(); 46 Enum_Test_t tmpGetPointer0 = *tmpGetPointerOut0; 47 Enum_Test_t tmpGetPointer1 = *tmpGetPointerOut1; 48 Enum_Test_t tmpGetPointer2 = *tmpGetPointerOut2; 49 Enum_Test_t tmpGetPointer3 = *tmpGetPointerOut3; 50 db_printf("Enum_Test_Get_Enum_Pointer() = %d\n", tmpGetPointer0); 51 db_printf("Enum_Test_Get_Enum_Pointer() = %d\n", tmpGetPointer1); 52 db_printf("Enum_Test_Get_Enum_Pointer() = %d\n", tmpGetPointer2); 53 db_printf("Enum_Test_Get_Enum_Pointer() = %d\n", tmpGetPointer3);
实验用的编译器为ARM Compiler V6.16,板子是stm32f103c8t6的blue pill系统板,4种结果如下图:




可以看到2个跟预期不一致的地方:
1.库和APP对优化选项不一致的话,压根无法连接
2.库和APP都优化的话,库确实优化了,测试的枚举size变成了1,但APP里的size又是4,不过下面的运行结果又是正确的
对于第1点,这个在上述的选项说明里有明确提及:

对于第2点,没有找到任何相关的说明,那只能找下代码看下为啥在库优化,APP不优化的情况下,一切输出还正常的原因
对于b传入指针的情况,用的是小端系统,所以没问题
对于c返回变量的情况,虽然库里那枚举size是1的,但耐不住人家return时候,把r0设置成0xFFFFFFFF而不是0x000000FF,所以没问题。

对于d返回指针的情况,编译器不是主动获取4字节数据,而是先获取int8_t数据后,通过符号位扩展成int32_t,所以也没问题

既然如此,尝试下换个编译器,改成ARMCC V5.06 update 7的,对于二者枚举down size优化的选项差异,在这文档有描述:

做完4组实验下来,发现现象和ARMCC V6.16的结果完全一致。
那如果混用,结果是不是也是一样呢,跨了编译器版本的优化选项,是否能绕过链接器的识别呢。做完8组实验发现,无论用什么编译器,最终结果是完全一致的,整体见下表:
|
序 号 |
库 | APP | 结果 | |||||
| 编译器版本 | 枚举优化 | 编译器版本 | 枚举优化 | 连接 | 库枚举大小 | APP枚举大小 | 程序现象 | |
| 1 | V6.16 | 否 | V6.16 | 否 | 成功 | 4 | 4 | 正确 |
| 2 | 否 | 是 | 失败 | / | ||||
| 3 | 是 | 否 | 失败 | / | ||||
| 4 | 是 | 是 | 成功 | 1 | 4 | 正确 | ||
| 5 | V5.06 | 否 | V5.06 | 否 | 成功 | 4 | 4 | 正确 |
| 6 | 否 | 是 | 失败 | / | ||||
| 7 | 是 | 否 | 失败 | / | ||||
| 8 | 是 | 是 | 成功 | 1 | 4 | 正确 | ||
| 9 | V6.16 | 否 | V5.06 | 否 | 成功 | 4 | 4 | 正确 |
| 10 | 否 | 是 | 失败 | / | ||||
| 11 | 是 | 否 | 失败 | / | ||||
| 12 | 是 | 是 | 成功 | 1 | 4 | 正确 | ||
| 13 | V5.06 | 否 | V6.16 | 否 | 成功 | 4 | 4 | 正确 |
| 14 | 否 | 是 | 失败 | / | ||||
| 15 | 是 | 否 | 失败 | / | ||||
| 16 | 是 | 是 | 成功 | 1 | 4 | 正确 | ||
综上,实验表明目前主流的编译器已经标记了生成的Object或Library文件是否采用了enum donw-size优化,而链接器则避免了枚举优化策略不一致的文件的连接,所以现在看来,enum的Reserved哪怕不写也不会引起什么程序bug,核心还是在于编译器对枚举优化策略是否一致。但是,个人建议,为了写代码时候能对使用的枚举变量size做到心中有数,这个Reserved还是写一下比较好,起码感官上不至于有一种被编译器喧宾夺主的feel
浙公网安备 33010602011771号