如何在C语言中声明具有唯一性的结构体类型?
背景
我正在使用一组自定义的不透明类型(下称“该集合”)。程序上层只传递集合内各类型对象的句柄,而知晓类型细节的下层代码则负责处理对应底层结构体并执行操作。
采用结构体指针的原因之一是遵循C标准(N2176 6.2.5-28):
所有指向结构体类型的指针应具有相同的表示形式和对齐要求。
我希望集合内每个类型都是唯一的(无继承、无多态),以此利用编译时类型错误检测。但我不太清楚“唯一”和“不兼容”的区别,根据标准(N2176 6.2.7-1):
若两种类型相同,则它们是兼容类型。
头文件示例如下:
// FILE: value.h #include <stddef.h> typedef struct s_myValue myValue; typedef myValue * myHandle; typedef const myHandle constMyHandle; // 这里有问题,typedef的const用法需注意 int value_init(myHandle, size_t); int value_f1(myHandle, ...); int value_f2(myHandle, ...);
或者另一种写法:
// FILE: value-1.h #include <stddef.h> typedef struct s_myValue myValue; int value_init(myValue *, size_t); int value_f1(myValue *, ...); int value_f2(myValue *, ...); int value_f3(const myValue *, ...);
(感谢Stack Overflow用户提供这类typedef写法,暂未找到更优方案。)
在某个场景中,我决定在下层代码中将myHandle转换为void *做内部处理。因此我认为,myValue、myHandle和constMyHandle的定义仅用于接口需求,struct s_myValue的具体实现无关紧要。
但核心问题是:如何保证集合中的所有类型都是唯一的?
受C标准(N2176 6.7.2.3-5)启发:
每个未包含标签的结构体、联合体或枚举类型声明都定义了一个唯一类型。
结合声明语法规范(N2176 A.2.2),我尝试了两种声明方式:
声明1(不符合标准)
// FILE: value.c #include "value.h" // ... struct s_myValue { _Static_assert(1, ""); }; // ...
这种写法不仅笨拙,还不符合C标准(N2176 6.2.5-20):
结构体类型描述了一组按顺序分配的非空成员对象……每个成员可指定名称,且类型可能不同。
声明2(写法糟糕)
// FILE: value.c #include "value.h" // ... struct s_myValue { // 随机生成的标识符(基于UUID4重排) s_myValue *a6e64fd2eb4689eab294b9524e0efa1; }; // ...
这种方式可读性极差,显然不是好方案。
问题
有没有更具表达性、更优雅的方式声明一个与其他所有类型都不同的结构体?有没有更符合C语言设计思路的写法?(没错,我知道把“设计”和“C”放一起有点奇怪。)
我认为struct s_myValue { };不可行,因为它不符合C标准。
附录:最小可复现示例
以下示例用于展示当前类型检查的行为:
//==> bar.h <== #ifndef H_BAR #define H_BAR typedef struct s_bar bar; void bar_init(bar *); #endif //==> bar.c <== #include "bar.h" struct s_bar { int i; }; void bar_init(bar *b) { ; } //==> baz.h <== #ifndef H_BAZ #define H_BAZ typedef struct s_baz baz; void baz_init(baz *); #endif //==> baz.c <== #include "baz.h" struct s_baz { int i; }; void baz_init(baz *b) { ; } //==> foo.h <== (空文件) //==> foo.c <== #include "bar.h" #include "baz.h" #include <stdlib.h> int main(void) { bar *pbar = NULL; // 简化示例,初始化函数无实际操作 baz *pbaz = NULL; bar_init(pbar); baz_init(pbar); // 故意传入错误类型的指针 return 0; } //==> Makefile <== objects=foo.o bar.o baz.o CC=gcc -std=c17 CCC=$(CC) -c foo: $(objects) $(CC) -o foo $(objects) foo.o: foo.c foo.h bar.h baz.h $(CCC) foo.c foo.h: ; foo.c: ; bar.o: bar.c bar.h $(CCC) bar.c bar.h: ; bar.c: ; baz.o: baz.c baz.h $(CCC) baz.c baz.h: ; baz.c: ;
编译时GCC仅发出警告而非错误:
foo.c: In function ‘main’: foo.c:9:16: warning: passing argument 1 of ‘baz_init’ from incompatible pointer type [-Wincompatible-pointer-types] 9 | baz_init ( pbar ) ; // 故意传入错误类型的指针 | ^~~~ | | | bar * {aka struct s_bar *} In file included from foo.c:2: baz.h:4:17: note: expected ‘baz *’ {aka ‘struct s_baz *’} but argument is of type ‘bar *’ {aka ‘struct s_bar *’} 4 | void baz_init ( baz * ) ; | ^~~~~
这并非我期望的严格类型检查——或许我应该转用C++?根据C标准(N2176 6.3.2.3-7):
指向某对象类型的指针可转换为指向另一对象类型的指针。
内容的提问来源于stack exchange,提问作者Ana Nimbus

