Arduino嵌入式C语言中联合体成员指向自身实例的类型安全解决方法
类型安全解决方案:利用C语言的不完全类型声明
你遇到的是C语言里递归数据结构定义的典型问题——子菜单需要指向联合体类型的数组,但联合体的定义在子菜单之后,导致编译器无法识别类型。好消息是,完全不需要用void*牺牲类型安全,通过不完全类型声明就能完美解决。
核心思路
在定义ts_EntrySubmenu结构体之前,先给tuEntry联合体做一个不完全类型声明。这个声明告诉编译器“这个联合体类型是存在的,后面会给出完整定义”,这样你就可以在ts_EntrySubmenu里直接使用tuEntry*类型的指针,而不会触发编译错误。
修改后的代码示例
这里我也修正了原代码里的一个语法错误(C语言不允许在结构体定义时直接给成员赋值const te_UIEntryType entryType = UI_ENTRY_SUBMENU;),并调整了子菜单的指针类型:
typedef enum {UI_STATE_HOME, UI_STATE_MENU, UI_STATE_DIALOG} te_UIState; typedef enum {UI_ENTRY_SUBMENU, UI_ENTRY_NUMERIC_INT, UI_ENTRY_NUMERIC_FLOAT, UI_ENTRY_BOOL, UI_ENTRY_DISCRETE} te_UIEntryType; // 关键步骤:提前声明联合体的不完全类型 typedef union tuEntry tuEntry; /* Typedefs for the different types of entry */ typedef struct { char* entryName; // pointer to one of our string entries, eg MNU_Light te_UIEntryType entryType; } tsEntry; typedef struct { char* entryName; te_UIEntryType entryType; int (* finalIntHandler)(int selectedValue); //The function that should be called when a number has been selected int (* initialIntValue)(); //The function that should be called to obtain the starting value for the selector } ts_EntryInt; typedef struct { char* entryName; te_UIEntryType entryType; int (* finalIntHandler)(float selectedValue); float (* initialSingleValue)(); } ts_EntrySingle; typedef struct { char* entryName; te_UIEntryType entryType; int (* finalIntHandler)(bool selectedValue); bool (* initialBoolValue)(); } ts_EntryBool; typedef struct { char* entryName; te_UIEntryType entryType; int (* handler)(); char* (* optionalEntryNamePtrFunction)(); //If this points to a function, it'll be called to determine what text to display as the entry name. // This is for things like enable/disable where the text changes depending on the present state. } ts_EntryDiscrete; // 修改子菜单结构体:用tuEntry*代替void*,移除非法的成员赋值 typedef struct { char* entryName; te_UIEntryType entryType; tuEntry* entries; // 指向子菜单的条目数组,类型安全 int entryCount; // 建议增加这个字段,记录子菜单的条目数量,方便遍历 } ts_EntrySubmenu; // 现在可以完整定义联合体了 typedef union tuEntry { tsEntry entry; ts_EntryInt entryInt; ts_EntrySingle entrySingle; ts_EntryBool entryBool; ts_EntryDiscrete entryDiscrete; ts_EntrySubmenu entrySubmenu; } tuEntry;
为什么这样可行?
- 不完全类型声明
typedef union tuEntry tuEntry;只是告诉编译器“有个叫tuEntry的联合体”,不需要立刻知道它的内部结构。因为我们在ts_EntrySubmenu里用的是指针类型tuEntry*,编译器只需要知道这个类型存在,就能处理指针的存储(指针大小在C里是固定的)。 - 当后面完整定义
tuEntry联合体时,编译器已经知道ts_EntrySubmenu的结构,所以可以正确解析联合体里的entrySubmenu成员。
初始化示例(类型安全)
现在你可以安全地创建层级菜单了,比如:
// 定义子菜单条目 tuEntry subMenuEntries[] = { {.entryInt = {"Set Brightness", UI_ENTRY_NUMERIC_INT, brightnessHandler, getCurrentBrightness}}, {.entryBool = {"Auto Mode", UI_ENTRY_BOOL, autoModeHandler, getAutoModeState}} }; // 定义主菜单条目,其中一个指向子菜单 tuEntry mainMenuEntries[] = { {.entrySubmenu = {"Light Settings", UI_ENTRY_SUBMENU, subMenuEntries, sizeof(subMenuEntries)/sizeof(tuEntry)}}, {.entryDiscrete = {"System Info", UI_ENTRY_DISCRETE, showSystemInfo, NULL}} };
这样既保持了类型安全,又完美解决了循环依赖的编译问题,完全不需要用void*来妥协。
内容的提问来源于stack exchange,提问作者Craig Graham
相关产品推荐
相关产品推荐

