Now, modular programming is possible in C, but only if the programmer sticks to some fairly rigid rules:
Exactly one header file per module. The header should contain the function prototypes and typedef declarations to be exported, and nothing else (except comments).
The comments in a header file should be all that an external caller needs to know about the module. There should never be any need for writers to know anything about the module except what is contained in the header file.
Every module must import its own header file, as a consistency check.
Each module should contain #include lines for anything being imported from another module, together with comments showing what is being imported. The comments should be kept up-to-date. There should be no reliance on hidden imports which occur as a consequence of the nested #include lines which typically occur when a header file needs to import a type definition or a constant from elsewhere.
Function prototypes should not be used except in header files. (This rule is needed because C has no mechanism for checking that a function is implemented in the same module as its prototype; so that the use of a prototype can mask a “missing function” error.)
Every global variable in a module, and every function other than the functions exported via the header file, should be declared static.
The compiler warning “function call without prototype” should be enabled, and any warning should be treated as an error.
For each prototype given in a header file, the programmer should check that a non-private (i.e. non-static, in the usual C terminology) function with precisely the same name has its implementation in the same module. (Unfortunately, the nature of the C language makes an automatic check impossible.)
Any use of grep should be viewed with suspicion. If a prototype is not in the obvious place, that’s probably an error.
Ideally, programmers working in a team should not have access to one another’s source files. They should share only object modules and header files.