You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C语言中声明具有唯一性的结构体类型?

如何声明唯一的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 02:51:08