possible flaws in 'including *.c files' style C programming - c

I came across some codes in the following way
//file.c
#include <stdlib.h>
void print(void){
printf("Hello world\n");
}
and
//file main.c
#include <stdio.h>
#include "file.c"
int main(int argc, char *argv[]){
print();
return EXIT_SUCCESS;
}
Is there any flaw in this kind of programming style? I am not able to make out the flaw although I feel so, because somewhere I read that separating the implementation into *.h and *.c file helps compiler check for consistency. I don't understand what is meant by consistency.
I would be deeply thankful for some suggestions.
--thanks

Nothing prevents you from including .c files. However, separating declaration (in .h files) and implementation (and .c files) and then including only .h files has several advantages :
Compile time. Your declaration usually changes less than your implementation. If you include only .h files, and makes a change in your implementation (in the .c file), then you only have to recompile one .c file, instead of all the files which include the modified file.
Readability and management of interfaces. All your declarations can be checked in a single glance at the (usually) small .h file whereas the .c file is filled with lines and lines of code. Moreover it helps you determine which file see which functions and variables. For example, to avoid having a global variable included where you don't want it.

It's a common expectation that the compiler should compile .c files. .h files are not directly given to the compiler. They are usually only included within .c files.
Thus, your code is expected to compile by something like:
gcc main.c file.c
rather than only gcc main.c. That command would fail in the linking stage as it sees duplicate symbols.

You're going to have problems if you include file.c in more than one source code file which combine to make a library/executable, since you'll have duplicate method implementations. The above strikes me as a poor means of sharing/reusing code, and is not to be recommended.

It is not uncommon to include data in another file if it is more convenient to separate it from the code. For example, XPM or raw BMP data in a char array could be included to embed an image in the program in this way. If the data is generated from another build step then it makes sense to include the file.
I would suggest using a different file extension to avoid confusion (e.g. *.inc, *.dat, etc.).

Yes, this is permitted.
Using this is an advanced topic.
It slows down development compile time (cheaper to compile only what is necessary).
It speeds up deployment compile time (all files are out of date).
It allows the compiler to inline functions across module boundaries.
It allows a trick to control exported symbols from a library while keeping it modular.
It might confuse the debugger.

There is nothing wrong from a C language perspective with including a .c file in a program. The C language cares not about the extension of the file, C++ in fact often omits an extension on certain header files to avoid conflicts.
But from the perspective of a programmer, yeah this is odd. Most programmers will operate on the assumption that a .c file will not be included and this can cause problems via incorrect assumptions. It's best to avoid this practice. If you find you must use it, it's a sign of poor design.

In the .h files you should place function prototypes. For example, in your code you should have:
//file.h
void print(void);
//file.c
void
print(void)
{
printf("Hello world\n");
}
//file main.c
#include <stdio.h>
#include "file.h"
int main(int argc, char *argv[]){
print();
return EXIT_SUCCESS;
}

There are only two reasons that I know of for including C files (and which make sense):
inline functions which are non trivial, but that's really a matter of style
share implementation of private (static) functions by including the same file in several other files. That's actually the only way to do it in a purely platform independent way (but toolchain specific tricks like hidden attribute for gcc, etc... are much better if they are available)
The flaws:
you compile several times the same code
if not used sparingly, it quickly leads to multiple defined symbols for public symbols, in a way which is difficult to debug (include files which include other files...)

It is bad style, but one other reason to do it is that it can be used a part of a trick using the ## token concatenation operator to do a kind of poor man's templating in C.
Note that this fairly evil, isn't recommended, and will produce code that's hard to debug and maintain, but you can do something like the following:
mytemplate.c:
MyTemplateFunction_ ## MYTYPE(MYTYPE x)
{
// function code that works with all used TYPEs
}
main.c:
#define MYTYPE float
#include "mytemplate.c"
#undef MYTYPE
#define MYTYPE int
#include "mytemplate.c"
#undef MYTYPE
int main(int, char*)
{
float f;
int i;
MyTemplateFunction_float(f);
MyTemplateFunction_int(i);
return 0;
}

The evils of macros can be exacerbated:
file1.c
#define bottom arse
file2.c
int f()
{
int arse = 4;
bottom = 3;
printf("%d", arse);
}
main.c
#include "file1.c"
#include "file2.c"
void main()
{
f();
}
Indeed a convoluted example. But usually you wouldn't notice it because a macro's scope is the file it is in.
I did actually get this bug, I was importing some lib code into a new project and couldn't be bothered to write the Makefile, so I just generated an all.cpp which included all the sources from the library. And it didn't work as expected due to macro pollution. Took me a while to figure it out.

It's fine for the programs of this length.

Related

is it possible to have only header file in C without source file

I would like to write a C library with fast access by including just header files without using compiled library. For that I have included my code directly in my header file.
The header file contains:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#ifndef INC_TEST_H_
#define INC_TEST_H_
void test(){
printf("hello\n");
}
#endif
My program doesn't compile because I have multiple reference to function test(). If I had a correct source file with my header it works without error.
Is it possible to use only header file by including code inside in a C app?
Including code in a header is generally a really bad idea.
If you have file1.c and file2.c, and in each of them you include your coded.h, then at the link part of the compilation, there will be 2 test functions with global scope (one in file1.c and the other one in file2.c).
You can use the word "static" in order to say that the function will be restricted so it is only visible in the .c file which includes coded.h, but then again, it's a bad idea.
Last but not least: how do you intend to make a library without a .so/.a file? This is not a library; this is copy/paste code directly in your project.
And when a bug is found in your "library", you will be left with no solution apart correcting your code, redispatch it in every project, and recompile every project, missing the very point of a dynamic library: The ability to "just" correct the library without touching every program using it.
If I understand what you're asking correctly, you want to create a "library" which is strictly source code that gets #incuded as necessary, rather than compiled separately and linked.
As you have discovered, this is not easy when you're dealing with functions - the compiler complains of multiple definitions (you will have the same problem with object definitions).
You have a couple of options at this point.
You could declare the function static:
static void test( void )
{
...
}
The static keyword limits the function's visibility to the current translation unit, so you don't run into multiple definition errors at link time. It means that each translation unit is creating its own separate "instance" of the function, leading to a bit of code bloat and slightly longer build times. If you can live with that, this is the easiest solution.
You could use a macro in place of a function:
#define TEST() (printf( "hello\n" ))
except that macros are not functions and do not behave like functions. While macro-based "libraries" do exist, they are not trivial to implement correctly and require quite a bit of thought. Remember that macro arguments are not evaluated, they're just expanded in place, which can lead to problems if you pass expressions with side effects. The classic example is:
#define SQUARE(x) ((x)*(x))
...
y = SQUARE(z++);
SQUARE(z++) expands to ((z++)*(z++)), which leads to undefined behavior.
Separate compilation is a Good Thing, and you should not try to avoid it. Doing everything in one source file is not scalable, and leads to maintenance headaches.
My program do not compiled because I have multiple reference to test() function
That is because the .h file with the function is included and compiled in multiple C source files. As a result, the linker encounters the function with global scope multiple times.
You could have defined the function as static, which means it will have scope only for the curent compilation unit, so:
static void test()
{
printf("hello\n");
}

c includes, preventing redundant code

If I have a c project where my main program needs file1 and file2 but file2 also needs file1. Is there a way I can get around including file2 in both main and file1? If I have an include guard, will this prevent file1.c from being added twice?
//file1.h
#ifndef FILE1_H
#define FILE1_H
void func1(void);
#endif
--
//file1.c
#include "file1.h"
void func1(void) {
..do something
}
--
//file2.h
#ifndef FILE2_H
#define FILE2_H
void func2(void);
#endif
--
//file2.c
#include "file2.h"
#include "file1.h"
void func2(void) {
..do something
func1();
}
--
//main.c
#include "file1.h"
#include "file2.h"
int main(void) {
func1();
func2();
return 0;
}
-- Since file2 includes file1, can I do this? will it prevent repetition of file1 code?
//main.c (alternate)
#include "file2.h"
int main(void) {
func1();
func2();
return 0;
}
I'm not too concerned about problems arising if file2 decides to no longer include file1 in the future. I'm much more concerned with wasted space.
What I'd like to know is A: does the include guard prevent the code duplication and if so, there is no additional space used by including file1 in both main.c and file2.c. B: in the case that extra space is being used, will my alternate main.c work?
Quick explanation (with the note that all of this can be overwritten by people that know what they are doing):
First of all, two definitions: declaration is when you write down that something exists. For example, "int foo();" or "struct bar;". Note that we can't actually use this thing yet, we've just given it a name. As long as you declare them as the same thing, you can declare things as many times as you want! (variable declaration has its own rules).
Anything you want to use needs to be declared before you reference it.
definition is when you say what the declaration is. int foo() {asdfadf;} or struct bar{int x;}. Things can be (and often are) defined when they are declared, but not always.
In C, you must follow the One Definition Rule. Things can be declared as often as you like, but they can be only defined once per translation unit (defined in one sec). (in addition, function calls can only be declared once per entire executable).
There are very few things that need to be defined before you use them...other than variables, you only need to define a struct before you use it in a context where you need its size or access to its members.
What is a translation unit? It is all the files used to compile a single source file. Your header files aren't targeted for compilation. Only your .c files (called "source files") are. For each c file, we have the idea of a "translation unit", which is all the files that are used to compile that c file. The ultimate output of that code is a .o file. A .o files contains all the symbols required to run the code defined in that c++ file. So your c file and any files included are withing the header file. Note: not everything declared in the translation unit needs to be defined in it to get a valid .o file.
So what is in a header file? Well (in general) you have a few things:
function declarations
global definitions & declarations
struct definitions & declarations
Basically, you have the bare bones declarations and definitions that need to be shared between the translation units. #include allows you to keep this in one shared file, rather than copying and pasting this code all over.
Your definitions can only happen once, so a include guard prevents that from being a problem. But if you only have declarations, you don't technically need and include guard. (You should still use them anyway, they can limit the cross-includes you do, as well as work as a guarantee against infinitely recursive inclusion). However, you do need to include all declarations relative to each translation unit, so you will most likely include it multiple times. THIS IS OK. At-least the declaration is in one file.
When you compile a .o file, the compiler checks that you followed the one definition rule, as well as all your syntax is correct. This is why you'll get these types of errors in "creating .o" steps of compilation.
So in your example, after we compile, we get file1.o (containing the definition of func1), file2.o (containing the definition of func2), and main.o (containing the definition of main). The next step is to link all these files together, using the linker. When we do, the compiler takes all these .o files, and makes sure that there is only one definition for each function symbol in the file. This is where the magic of letting main.o know what is in file1.o and file2.o happens: it resolves the "unresolved symbols" and detects when there are conflicting symbols.
Final Thought:
Keeping code short is kindof a misguided task. You want your code to be maintainable and readable, and making the code as short as possible is about the opposite of that. I can write a whole program on one line with only single letter alpha-numberic variables names, but no one would ever know what it did...what you want to avoid is code duplication in things like declarations. Maintaining a long list of #includes can become tricky, so it is often good to group related functions together (A good rule of thumb is that if I almost always use A and B together) then they should probably be in the same header file.
Another thing I occasionally (occasionally because it has some serious drawbacks) is to use a convenience header file:
//convience.h
#ifndef CONVIENIENCE_H
#define CONVIENIENCE_H
#include "file1.h"
#include "file2.h"
#endif
The convenience header file only has other header files in it, which ensures that it NEVER contains code, which makes it a little easier to maintain, but still kindof a mess. Also note that if you do the include guards in file1 and file2, the convienience guard isn't nessisary, though it can (theoretically) speed up compilation.
Why can't you have a single header where you can put both your functions func1() and func2().
Just include the header in different files.
Didn't get what you mean by code duplication.
//file1.h
extern void func1();
extern void func2();
//file1.c
#include<file1.h>
void func1()
{`
enter code here`
}
//file2.c
#include<file1.h>
void func2()
{
}
//main.c
#include <file1.h>
main()
{
func1();
func2();
}

Multiple Inheritance in C

I am stuck with a classical Multiple Inheritance problem in C.
I have created source files Stack.c and Queue.c. Both of them #include a file Node.c (which containing functions to allocate and deallocate memory). Now, I am trying to implement another program in a single file, for which I need to include both Stack.c and Queue.c.
I tried to #include both the files, but the compiler is throwing a conflicting type error.
What is the most correct way to do so?
Thanks in advance!!
Calling this "multiple inheritance" may be confusing because multiple inheritance is an object-oriented programming issue that doesn't arise in C.
It appears to me that your difficulty may be that you are trying to #include executable code (i.e. .c files) instead of linking the .c files and #including header (.h) files that provide declarations for the functions in the .c files.
This will happen if you #include source files (.c)... you are supposed to (for the most part) #include headers (.h). Headers generally provide function prototypes, typedefs, macros, etc. but leave out the actual implementation.
The actual implementation of functions, definitions of variables, etc. should happen exactly once per-compilation unit and usually in a .c file.
If you have other code that needs to re-use functions or variables defined in another compilation unit (e.g. Stack.c), you would #include Stack.h which would provide the function prototypes, global variable names, etc. that you might need.
Once you compile all of your compilation units, it is the linker's job to figure out which object file or library a function or variable is defined in. You drastically complicate its job when you #include "X.c" in another compilation unit, because then you wind up with multiple locations for the same thing (symbols, as the linker likes to call them).
In short, use headers and let the linker do its job.
On a related note, this has nothing to do with multiple-inheritance. That is an object-oriented issue, for languages like C++. The proper name for what you are describing is "symbol collision" or "duplicate symbols".
Without seeing details of your program, it is difficult to say. However, conflicting type errors can often be fixed simply by rearranging your code or adding function prototypes.
You see, functions need to be described before they are invoked when reading the source file top to bottom. e.g.:
char foo1 ()
{
char blah = foo2();
return blah;
}
char foo2 ()
{
return 'a';
}
You'd get a conflicting type error because when it's inside foo1, it hasn't seen the declaration for foo2 yet. Thus it assumes that whatever foo2 is, it will return an int. But in actuality it returns a char. These two aren't the same, so... a conflicting type error is reported.
You can fix this by having foo2 come first in the source code, or by inserting a function prototype:
char foo2 (); // function prototype
char foo1 ()
{
char blah = foo2();
return blah;
}
char foo2 ()
{
return 'a';
}
You can also get a conflicting type error if you include source files, for the same reason. #include "Node.c" is essentially a copy-paste. It would be a good idea to switch from including Node.c to including Node.h with externalized functions. You can also avoid a lot of problems if you give prefixes to your function names in source files you plan to include, like.... nodeInsert, nodeDelete, nodeCompare, etc.

Best practice for using includes in C

I am learning C and I am unsure where to include files. Basically I can do this in .c or in .h files:
Option 1
test.h
int my_func(char **var);
test.c
#include <stdio.h>
#include "test.h"
int my_func(char **var) {printf("%s\n", "foo");}
int main() {...}
Option 2
test.h
#include <stdio.h>
int my_func(char **var);
test.c
#include "test.h"
int my_func(char **var) {printf("%s\n", "foo");}
int main() {...}
With option 2 I would only need to include test.h in whatever .c file I need the library. Most of the examples I see use option 1.
Are there some general rules when to do what or is this a question of personal preferences?
Don't use includes, you don't need.
I'd choose something like "Option 1". Why "something like" ? Because I'd create a separate file for the main and I'd keep all declaraions inside the .h and all definitions inside the corresponding .c.
Of course, both options are valid.
If you want to include only one header in your main, you can just create a header file, containing only includes - that's a common practice. This way, you can include only one header, instead of several.
I tend to prefer Option 1, as cyclic dependencies will come and bite you very quickly in option 2, and reducing the input size is the best way to guarantee faster compile times. Option 2 tends towards including everything everywhere, whether you really need it or not.
That said, it might be best to experiment a little with what works for structuring your projects. Hard and fast rules tend to not apply universally to these kinds of questions.
both options are correct. the C standard allows both solutions
All C standard headers must be made such that they can be included several times and in any order:
Standard headers may be included in any order; each may be included
more than once in a given scope, with no effect different from being
included only once
(From Preprocessor #ifndef)
I don't think that there is a universal rule (from a "grammatical" point of view, both your options are correct and will work). What is often done is to include in the .h file the headers you need for the library (as in your option 1), either because you'll need them when working with the library (thus avoiding always including the same set of header files in your .c files, which is more error prone), or because they are mentioned in the .h file itself (e.g., if you use a int32_t as type in the function prototypes of the .h files, you will of course need to include <stdint.h> in the .h file).
I prefer to use includes in c file.
If your program is getting bigger you might forgot to include something in one header file, but it is included in one other you use.
By including them in c-file you won't lose includes, while editing other files.
I prefer Option 1. I want to know what I used in my project, and in much time, Option 1 works more effective than Option 2 in time and efficiency.
There is no rule specifying you have following a particular fashion. Even if you include / not include in test.c file, it is not going to bother much, provided you include it in test.h file and include that test.h file in test.c. Hope you are clear with that.
This is because, you have preprocessor directives like #ifndef, #define, #endif. They are called Include guards. These are used in the inbuild header files. However, when you include a file written by you, either go with your option 2, or use include guards to be safe.
The include guards work as follows.
#ifndef ANYTHING
#define ANYTHING
..
..
..
#endif
So when you include for the first time, ANYTHING is not yet defined. So ifndef returns true, then ANYTHING gets defined, and so...on.. But the next time if you include the same file ( by mistake) ifndef would return a false since ANYTHING is now defined and so the file would not be included at all. This protection is necessary to avoid duplicate declarations of variable present in the header file. That would give you a compilation error.
Hope that helps
Cheers

Function prototype in header file doesn't match definition, how to catch this?

(I found this question which is similar but not a duplicate:
How to check validity of header file in C programming language )
I have a function implementation, and a non-matching prototype (same name, different types) which is in a header file. The header file is included by a C file that uses the function, but is not included in the file that defines the function.
Here is a minimal test case :
header.h:
void foo(int bar);
File1.c:
#include "header.h"
int main (int argc, char * argv[])
{
int x = 1;
foo(x);
return 0;
}
File 2.c:
#include <stdio.h>
typedef struct {
int x;
int y;
} t_struct;
void foo (t_struct *p_bar)
{
printf("%x %x\n", p_bar->x, p_bar->y);
}
I can compile this with VS 2010 with no errors or warnings, but unsurprisingly it segfaults when I run it.
The compiler is fine with it (this I understand)
The linker did not catch it (this I was slightly surprised by)
The static analysis tool (Coverity) did not catch it (this I was very surprised by).
How can I catch these kinds of errors?
[Edit: I realise if I #include "header.h" in file2.c as well, the compiler will complain. But I have an enormous code base and it is not always possible or appropriate to guarantee that all headers where a function is prototyped are included in the implementation files.]
Have the same header file included in both file1.c and file2.c. This will pretty much prevent a conflicting prototype.
Otherwise, such a mistake cannot be detected by the compiler because the source code of the function is not visible to the compiler when it compiles file1.c. Rather, it can only trust the signature that has been given.
At least theoretically, the linker could be able to detect such a mismatch if additional metadata is stored in the object files, but I am not aware if this is practically possible.
-Werror-implicit-function-declaration, -Wmissing-prototypes or equivalent on one of your supported compilers. then it will either error or complain if the declaration does not precede the definition of a global.
Compiling the programs in some form of strict C99 mode should also generate these messages. GCC, ICC, and Clang all support this feature (not sure about MS's C compiler and its current status, as VS 2005 or 2008 was the latest I've used for C).
You may use the Frama-C static analysis platform available at http://frama-c.com.
On your examples you would get:
$ frama-c 1.c 2.c
[kernel] preprocessing with "gcc -C -E -I. 1.c"
[kernel] preprocessing with "gcc -C -E -I. 2.c"
[kernel] user error: Incompatible declaration for foo:
different type constructors: int vs. t_struct *
First declaration was at header.h:1
Current declaration is at 2.c:8
[kernel] Frama-C aborted: invalid user input.
Hope this helps!
Looks like this is not possible with C compiler because of its way how function names are mapped into symbolic object names (directly, without considering actual signature).
But this is possible with C++ because it uses name mangling that depends on function signature. So in C++ void foo(int) and void foo(t_struct*) will have different names on linkage stage and linker will raise error about it.
Of course, that will not be easy to switch a huge C codebase to C++ in turn. But you can use some relatively simple workaround - e.g. add single .cpp file into your project and include all C files into it (actually generate it with some script).
Taking your example and VS2010 I added TestCpp.cpp to project:
#include "stdafx.h"
namespace xxx
{
#include "File1.c"
#include "File2.c"
}
Result is linker error LNK2019:
TestCpp.obj : error LNK2019: unresolved external symbol "void __cdecl xxx::foo(int)" (?foo#xxx##YAXH#Z) referenced in function "int __cdecl xxx::main(int,char * * const)" (?main#xxx##YAHHQAPAD#Z)
W:\TestProjects\GenericTest\Debug\GenericTest.exe : fatal error LNK1120: 1 unresolved externals
Of course, this will not be so easy for huge codebase, there can be other problems leading to compilation errors that cannot be fixed without changing codebase. You can partially mitigate it by protecting .cpp file contents with conditional #ifdef and use only for periodical checks rather than for regular builds.
Every (non-static) function defined in every foo.c file should have a prototype in the corresponding foo.h file, and foo.c should have #include "foo.h". (main is the only exception.) foo.h should not contain prototypes for any functions not defined in foo.c.
Every function should prototyped exactly once.
You can have .h files with no corresponding .c files if they don't contain any prototypes. The only .c file without a corresponding .h file should be the one containing main.
You already know this, and your problem is that you have a huge code base where this rule has not been followed.
So how do you get from here to there? Here's how I'd probably do it.
Step 1 (requires a single pass over your code base):
For each file foo.c, create a file foo.h if it doesn't already exist. Add "#include "foo.h" near the top of foo.c. If you have a convention for where .h and .c files should live (either in the same directory or in parallel include and src directories, follow it; if not, try to introduce such a convention).
For each function in foo.c, copy its prototype to foo.h if it's not already there. Use copy-and-paste to ensure that everything stays consistent. (Parameter names are optional in prototypes and mandatory in definitions; I suggest keeping the names in both places.)
Do a full build and fix any problems that show up.
This won't catch all your problems. You could still have multiple prototypes for some functions. But you'll have caught any cases where two headers have inconsistent prototypes for the same function and both headers are included in the same translation unit.
Once everything builds cleanly, you should have a system that's at least as correct as what you started with.
Step 2:
For each file foo.h, delete any prototypes for functions that aren't defined in foo.c.
Do a full build and fix any problems that show up. If bar.c calls a function that's defined in foo.c, then bar.c needs a #include "foo.h".
For both of these steps, the "fix any problems that show up" phase is likely to be long and tedious.
If you can't afford to do all this at once, you can probably do a lot of it incrementally. Start with one or a few .c files, clean up their .h files, and remove any extra prototypes declared elsewhere.
Any time you find a case where a call uses an incorrect prototype, try to figure out the circumstances in which that call is executed, and how it causes your application to misbehave. Create a bug report and add a test to your regression test suite (you have one, right?). You can demonstrate to management that the test now passes because of all the work you've done; you really weren't just messing around.
Automated tools that can parse C are likely to be useful. Ira Baxter has some suggestions. ctags may also be useful. Depending on how your code is formatted, you can probably throw together some tools that don't require a full C parser. For example, you might use grep, sed, or perl to extract a list of function definitions from a foo.c file, then manually edit the list to remove false positives.
Its obvious ("I have a huge code base") you cannot do this by hand.
What you need is an automated tool that can read your source files as the compiler sees them, collect all function prototypes and definitions, and verify that all definitions/prototypes match. I doubt you'll find such a tool lying around.
Of course, this match much check the signature, and this requires something like the compiler's front end to compare the signatures.
Consider
typedef int T;
void foo(T x);
in one compilation unit, and
typedef float T;
void foo(T x);
in another. You can't just compare the signature "lines" for equality; you need something that can resolve the types when checking.
GCCXML may be able to help, if you are using a GCC dialect of C; it extracts top-level declarations from source files as XML chunks. I don't know if it will resolve typedefs, though. You obviously have to build (considerable) support to collect the definitions in a central place (a database) and compare them. Comparing XML documents for equivalents is at least reasonably straightforward, and pretty easy if they are formatted in a regular way. This is likely your easiest bet.
If that doesn't work, you need something that has a full C front end that you can customize. GCC is famously available, and famously hard to customize. Clang is available, and might be pressed into service for this, but AFAIK only works with GCC dialects.
Our DMS Software Reengineering Toolkit has C front ends (with full preprocessing capability) for many dialects of C (GCC, MS, GreenHills, ...) and builds symbol tables with complete type information. Using DMS you might be able (depending on the real scale of your application) to simply process all the compilation units, and build just the symbol tables for each compilation unit. Checking that symbol table entries "match" (are compatible according to compiler rules including using equivalent typedefs) is built-into the C front ends; all one needs to do is orchestrate the reading, and calling the match logic for all symbol table entries at global scope across the various compilation units.
Whether you do this with GCC/Clang/DMS, it is a fair amount of work to cobble together a custom tool. So you have decide how critical you need for fewer suprises is, compared to the energy to build such a custom tool.

Resources