4

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?

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
thoni56
  • 3,145
  • 3
  • 31
  • 49
  • 1
    Though off-topic, there is a tool called `cflow` from GNU with this intent in mind. It will map the function calls within a source and provides several formats to tailor the information to your needs. – David C. Rankin Mar 03 '20 at 10:05
  • Thanks, that might also be interesting since I'm picking up an legacy project and trying to clean it up but also understand it better. – thoni56 Mar 03 '20 at 10:07
  • 2
    What you're trying to do makes little sense, functions have external linkage by default, so shopping for a tool to accommodate that style is going to be difficult. Perhaps focus a bit on what you really want to achieve, google for "c dead code identification". – Hans Passant Mar 03 '20 at 11:18
  • 1
    @HansPassant: I'm not sure I follow. I'm not looking for functions that are not used, I'm looking to have my .h files match what's intended to be "exported" from the corresponding .c file. "access to static functions is restricted to the file where they are declared" is the common view of functions inside C files declared `static`, linkers might do something else, but that is irrelevant for what I'm looking for. – thoni56 Mar 03 '20 at 11:27
  • 2
    Step 1. Extract function declarations from header and source file. Step 2. Compare the list. There is [makeheaders](https://www.hwaci.com/sw/mkhdr/index.html) and `cproto` and many programs that would allow you do step 1. Note that it is not trivial (actually very hard) to do it properly and you (almost) need full semantic compiler and preprocessor implemented. For example `int (*(func2(int x))(void)` or `#define macro abc` `int macro()`. [Extractin C/C++ function prototypes](https://stackoverflow.com/questions/1570917/extracting-c-c-function-prototypes) – KamilCuk Mar 03 '20 at 11:36
  • @KamilCuk `makeheaders` seems to do the exact opposite, generate one header file for each `.c` file but containing everything that file needs, and `cproto` also generates files but from the prototypes in the `.c` file. I'm starting to think I should start this project and build it on [pycparser](https://github.com/eliben/pycparser), worked with that before and it's fairly simple. – thoni56 Mar 03 '20 at 15:31
  • 2
    The reason your question was originally closed as off-topic as noted in my comment, is that it is specifically the type of question defined as off-topic for this site, one asking for a specific tool to do a job. See [Off-Topic #4](https://stackoverflow.com/help/on-topic). – David C. Rankin Mar 03 '20 at 17:37

4 Answers4

3

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 : )

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks for pointing to `ctags` which would definitely be one starting point. – thoni56 Mar 03 '20 at 12:14
  • Wow, I did not know that ctags is continued. What options to pass to ctags to get that? `ctags '--fields-C=+{properties}' -o - ./module.h` prints nothing. – KamilCuk Mar 04 '20 at 16:44
  • Universal-ctags (https://ctags.io) can store "static" modifier to the parser specific field called "properties:" So you can still use grep. $ cat foo.c static void foo (void) {} void bar (void) {} $ ./ctags '--fields-C=+{properties}' -o - --kinds-C=+p ./foo.c bar ./foo.c /^bar$/;" f typeref:typename:void foo ./foo.c /^foo $/;" f typeref:typename:void file: properties:static – Masatake YAMATO Mar 04 '20 at 16:48
0

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.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 2
    The question is asking how to gather information, which this post doesn't address. Hence, it does not provide an answer to the question. It is simply commentary on the idea of what OP is trying to do. – TylerH Mar 03 '20 at 16:32
0

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.

ljrk
  • 751
  • 1
  • 5
  • 21
  • Obviously I have to rethink how I formulate my problem better. I only want to ensure that definitions are in the .h which has the same name as the .c file. I always use `-Wall` and agree that you should always have those warnings turned on. But they only guarantee that there *is* such previous external declaration, not that it lives in the correct header file, which is exactly the problem I'm trying to solve. – thoni56 Mar 03 '20 at 16:08
  • 1
    @thoni56 ah, ensuring that it is declared in a file that's name the same way is indeed a different problem, I see. – ljrk Mar 03 '20 at 18:42
-1

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
thoni56
  • 3,145
  • 3
  • 31
  • 49