为何C语言泛型更常使用void*而非宏实现?
先看你提供的用宏实现泛型Option类型的代码:
#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #define OPTION(T) \ struct option_##T { \ bool exists; \ T data; \ }; \ typedef struct option_##T option_##T; \ static inline bool is_some_##T(option_##T x) { return x.exists; } \ static inline bool is_none_##T(option_##T x) { return !x.exists; } #define is_some(x) _Generic((x), option_int: is_some_int, option_char: is_some_char)(x) #define is_none(x) _Generic((x), option_int: is_none_int, option_char: is_none_char)(x) OPTION(int); OPTION(char); int main(int argc, char *argv[]) { option_int x = {.exists = true, .data=3}; option_char y = {.exists = true, .data='a'}; printf("x is some: %d\n", is_some(x)); printf("y is some: %d\n", is_some(y)); return 0; }
对比之下,void*方案更常见主要有这些原因:
避免代码膨胀
宏会为每个实例化的类型生成一套独立的结构体和函数,哪怕逻辑完全一致。如果用OPTION实例化十几种类型,二进制里会多出大量重复代码,体积直接飙升。而void*方案只需要一套代码,所有类型共用,二进制体积紧凑得多。通用性更强,无需提前绑定类型
void的泛型组件可以直接处理任意类型的数据,不用提前用宏预编译。比如一个基于void的链表,直接就能存int、char、自定义结构体的指针,不用为每种类型单独生成链表代码。宏方案必须提前知道要支持的类型,每次加新类型都要重新实例化宏,灵活性差很多。接口统一,维护成本低
void*方案对外提供的是统一接口,比如option_is_some(Option* opt),不管存的是int还是自定义类型,调用方式完全一致。宏方案的接口会随类型变化,哪怕用_Generic封装,新增类型时也要修改_Generic的分支,越到后面维护越麻烦。支持动态类型场景
如果需要处理动态类型(比如从文件读取不同类型的数据、做通用配置解析),void*方案可以配合类型标签(比如在结构体里加个enum标记数据类型)实现动态判断和转换。宏方案在编译期就绑定了类型,根本没法处理这类场景。兼容性拉满
虽然C11有_Generic,但一些老编译器(尤其是嵌入式环境的)可能不支持。而void*是C语言从早期就有的特性,几乎所有编译器都能兼容,不用担心环境适配问题。
这里也给一个void*实现Option类型的示例参考:
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> typedef struct { bool exists; void* data; } Option; static inline bool option_is_some(Option* opt) { return opt != NULL && opt->exists && opt->data != NULL; } static inline bool option_is_none(Option* opt) { return !option_is_some(opt); } Option* option_some(void* data) { Option* opt = malloc(sizeof(Option)); if (!opt) return NULL; opt->exists = true; opt->data = data; return opt; } Option* option_none(void) { Option* opt = malloc(sizeof(Option)); if (!opt) return NULL; opt->exists = false; opt->data = NULL; return opt; } int main() { int int_val = 3; char char_val = 'a'; Option* x = option_some(&int_val); Option* y = option_some(&char_val); printf("x is some: %d\n", option_is_some(x)); printf("y is some: %d\n", option_is_some(y)); free(x); free(y); return 0; }
内容的提问来源于stack exchange,提问作者Capybara

