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

Viewed 244

In C it is an idiomatic pattern to have your .h file contain declarations of the externally visible symbols in the corresponding .c file. The purpose if this is to support a kind of "module & interface" thinking, e.g enabling a cleaner structure.

In a big legacy C system I'm working on it is not uncommon that functions are declared in the wrong header files probably after moving a function to another module, since it still compiles, links and runs, but that makes the modules less explicit in their interfaces and indicates wrong dependencies.

Is there a way to verify / confirm / guarantee that the .h file has all the external symbols from .c and no external symbols that are not there?

E.g. if I have the following files

module.c

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

module.h

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

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

Modern compilers give some assistance in as so much that they can detect a missing declaration that you actually referenced, but it does not care from which file it comes.

What are my options, other than going over each pair manually, to obtain this information?

4 Answers

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

Using POSIX-ish linux with bash, diff and ctags and given really simple example of input files, you could do this:

$ #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 \1 is not externally visible symbol/; s/< (.*)/\1 is missing/'
func2 is missing
I would like to point the fact that func4 is not externally visible symbol

This will break if for example static keyword is not on the same line as function identifier is introduced, because ctags will not output it them. So the real job of this is getting the list of externally visible function declarations. This is not an easy task and writing such tool is left to others : )

It does not make any sense as if you call not defined function, the linker will complain.

More important is to have all functions prototypes - as compiler has to know how to call them. But in this case compilers emit warnings.

Some notes: you do not need the keyword extern as functions are extern by default.

This is the time to shine for some of my favorite compiler warning flags:

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

This at least ensures, that all the source files containing implementations of a function that is not static also have a previous external declaration & prototype of said function, ie. in your example:

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

If we'd provide just a forward declaration that doesn't constitute a full prototype we'd still get:

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;}
      |      ^~~~~

Only providing a full prototype will fix that warning. However, there's no way to make sure that all declared functions are actually also implemented. One could go about this using linker module definition files, a script using nm(1) or a simple "example" or unit test program, that includes every header file and tries to call all functions.

To list the differences between the exported symbols in a .c module in C and the corresponding .h file you can use chcheck. Just give the module name on the command line

python3 chcheck.py <module>

and it will list what externally visible functions are defined in the .c module but not exposed in the .h header file, and if there are any functions in the header module that are not defined in the corresponding .c file.

It only checks for function declarations/definitions at this point.

Disclaimer I wrote this to solve my own problem. Its built in Python on top of @eliben:s excellent pycparser.

Output for the example in the question is

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
Related