【发布时间】:2014-09-19 17:39:10
【问题描述】:
编辑:将 foo_t 更改为 foo 作为类型名,因为 POSIX 保留以 _t 结尾的类型 编辑:将 _foo_s 更改为 foo_s,因为 C 声明名称以下划线开头
我对同时拥有以下内容的最佳方法感到困惑:
- 库实现看到结构成员,但库的用户看不到
- 编译器检查头文件中的函数定义是否与实现匹配
- 使用 C99
我的第一次尝试是做以下事情:
foo.h(为简洁起见省略了包含保护):
typedef struct foo_s foo;
struct foo_s;
foo* foo_create(void);
void foo_do_something(foo *foo);
foo.c:
#include "foo.h"
struct foo_s {
/* some_hidden_members */
};
foo* foo_create() {
/* allocate memory etc */
}
void foo_do_something(foo *foo) {
/* do something with foo */
}
这似乎适用于 gcc。包括foo.h的人都只看到匿名前向声明,struct foo_s的真实布局只有foo.c知道。
当我尝试使用使用 clang 的 include-what-you-use 时,我开始闻到上面的一些奇怪的味道。当我用它检查foo.c 时,它告诉我foo.h 不应包含struct foo_s 的前向声明。我认为这是 iwyu 中的一个错误,因为显然这对于包含 foo.h 的其他任何人来说都不是问题。
在这一点上,让我从头来谈谈我的第二个要求。 foo.c 包含foo.h 以便编译器可以确保foo.h 中声明的每个函数都与foo.c 中的实现相匹配。我想我需要这个,因为我经常遇到分段错误,因为我的实现的函数签名与其他代码使用的标头中的函数签名不匹配。
后来我尝试用clang编译代码(我用-Wall -Wextra -Werror编译)并被告知:
error: redefinition of typedef 'foo' is a C11 feature
我不希望我的代码依赖于 C11 功能,我希望确保公共标头中的函数与实现相匹配。我该如何解决?
我看到一种方法是将foo.h 拆分为foo.h 和foo_private.h:
foo.h(为简洁起见省略了包含保护):
struct foo_s;
#include "foo_private.h"
foo_private.h(为简洁起见省略了包含保护):
typedef struct foo_s foo;
foo* foo_create(void);
void foo_do_something(foo *foo);
然后我会将foo_private.h 包含在foo.c 中,其他代码将包含foo.h。这意味着foo.c 不再看到foo_s 的前向声明,因此clang 和iwyu 应该很高兴。这也意味着我的函数的实现被检查以匹配标题。
虽然这可行,但我想知道这是否是最好的解决方案,因为:
- 一个头文件只有两行似乎很浪费
- 我不知道有其他类似的项目(在我的 /usr/include 中也没有看到)
那么满足顶部列出的三个标准的解决方案是什么?或者是我找到的解决方案?
【问题讨论】:
-
在
typedef struct _foo_s foo_t;之后不需要声明struct _foo_s;。单独的 typedef 已经声明了 bothstruct _foo_s和foo_t。以下struct _foo_s;没有添加任何内容。这是多余的。也许这正是 iwyu 想要告诉你的。 -
您显示的代码不会产生双重
typedef。如果您使用包含保护您的.h文件内容的守卫,您应该没有问题。 -
OT:POSIX 不允许以
_t结尾的类型。 -
@alk:这有点夸大了。 POSIX 保留以
_t结尾的类型名称,这意味着如果您创建自己的匹配类型名称,您随时可能遇到问题,这是您自己的错。这不像“禁止”那么严格,尽管它更加冗长。请注意,C 标准保留了大多数以下划线开头的名称,因此名称_foo_s是在类似的薄冰上行走。 (ISO/IEC 9899:§7.1.3 保留标识符:以下划线开头的所有标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。
标签: c gcc clang c99 forward-declaration