如何将 .h 文件中的外部符号验证为 .c 文件?

How to verify external symbols in an .h file to the .c file?

C 中,让您的 .h 文件包含相应 .c 文件中外部可见符号的声明是一种惯用模式。这样做的目的是为了支持一种 "module & interface" 思维,例如实现更简洁的结构。

在我正在处理的大型遗留 C 系统中,函数在错误的头文件中声明的情况并不少见,可能是在将函数移动到另一个模块之后,因为它仍然可以编译、链接和运行,但这使得模块在其接口中不太明确,并指示错误的依赖关系。

有没有办法验证/确认/保证 .h 文件具有来自 .c 的所有外部符号并且没有不存在的外部符号?

例如如果我有以下文件

module.c

int func1(void) {}
bool func2(int c) {}
static int func3(void) {}

module.h

extern int func1(void);
extern bool func4(char *v);

我想指出 func4 不是 module.c 中的外部可见符号,并且 func2 缺失。

现代编译器提供了一些帮助,它们可以检测到您实际引用的缺失声明,但它不关心它来自哪个文件。

除了手动检查每一对之外,我还有哪些选择可以获取此信息?

没有任何意义,好像你调用了未定义的函数,链接器会报错。

更重要的是拥有所有函数原型——因为编译器必须知道如何调用它们。但在这种情况下,编译器会发出警告。

一些注意事项:您不需要关键字 extern,因为函数默认为 extern

I want to be pointed to the fact that func4 is not an external visible symbol in module.c and that func2 is missing.

将 POSIX-ish linux 与 bashdiffctags 一起使用,并给出非常简单的输入文件示例,您可以这样做:

$ #recreate input
$ cat <<EOF >module.c
int func1(void) {}
bool func2(int c) {}
static int func3(void) {}
EOF
$ cat <<EOF >module.h
extern int func1(void);
extern bool func4(char *v);
EOF
$ # helper function for extracting only non-static function declarations
$ f() { ctags -x --c-kinds=fp "$@" | grep -v static | cut -d' ' -f1; }
$ # simply a diff
$ diff <(f module.c) <(f module.h)
2,3c2
< func2
---
> func4
$ diff <(f module.c) <(f module.h) |
> grep '^<\|^>' |
> sed -E 's/> (.*)/I would like to point the fact that  is not externally visible symbol/; s/< (.*)/ is missing/'
func2 is missing
I would like to point the fact that func4 is not externally visible symbol

如果 static 关键字与引入的函数标识符不在同一行,这将中断,因为 ctags 不会输出它们。所以真正的工作是获取外部可见函数声明的列表。这不是一件容易的事,编写这样的工具留给其他人:)

现在是展示我最喜欢的一些编译器警告标志的时候了:

CFLAGS += -Wmissing-prototypes \
  -Wstring-prototypes \
  -Wmissing-declarations \
  -Wold-style-declaration \
  -Wold-style-definition \
  -Wredundant-decls

这至少可以确保所有包含非 static 函数实现的源文件也具有该函数的先前外部声明和原型,即。在你的例子中:

module.c:4:6: warning: no previous prototype for ‘func2’ [-Wmissing-prototypes]
    4 | bool func2(int c) { return c == 0; }
      |      ^~~~~

如果我们只提供不构成完整原型的前向声明,我们仍然会得到:

In file included from module.c:1:
module.h:7:1: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
    7 | extern bool func2();
      | ^~~~~~
module.c:4:6: warning: no previous prototype for ‘func2’ [-Wmissing-prototypes]
    4 | bool func2(int c) { return c == 0;}
      |      ^~~~~

只有提供完整的原型才能修复该警告。然而,没有办法确保所有声明的函数实际上也被实现了。可以使用链接器模块定义文件、使用 nm(1) 的脚本或简单的 "example" 或单元测试程序来解决此问题,其中包括每个头文件并尝试调用所有函数。

要列出 C 中 .c 模块中导出的符号与相应的 .h 文件之间的差异,您可以使用 chcheck。只需在命令行中输入模块名称

python3 chcheck.py <module>

并且它会列出哪些外部可见的函数在.c模块中定义但没有暴露在.h头文件中,如果头模块中有任何函数没有定义在相应的 .c 文件中。

此时它只检查函数 declarations/definitions。

免责声明 我写这个是为了解决我自己的问题。它内置于 Python 之上 @eliben:s excellent pycparser.

问题中示例的输出是

Externally visible definitions in 'module.c' that are not in 'module.h':
  func2

Declarations in 'module.h' that have no externally visible definition in 'module.c':
  func4