I'm working on a large project in C, and I want to organize it using interface (.h) and implementation (.c) files, similar to many object-oriented languages such as Objective-C or Java. I am familiar with creating static libraries in C, but I think that doing so for my project is unnecessarily complex. How can I implement an interface/implementation paradigm in ANSI C? I'm primarily using GCC for compilation, but I'm aiming for strict adherence to ANSI C and cross-compiler compatibility. Thanks!
It sounds like you are already doing the right thing: good C code also organizes interfaces in .h-files and implementations in .c-files.
Example a.h file:
void f(int a);
Example a.c file:
#include "a.h"
static void helper(void) {...}
void f(int a) {... use helper()...}
Example main.c file:
#include "a.h"
int main(void) { f(123); return 0; }
You get modularity because helper-functions are not declared in headers so other modules dont know about them (you can declare them at the top of the .c
file if you want). Having this modularity reduces the number of recompiles needed and reduces how much has to be recompiled. (The linking has to be done every time though). Note that if you are not declaring helper-functions in the header then you are already pretty safe, however having the static
in front of them also hides them from other modules during linking so there is no conflict if multiple modules use the same helper-function-names.
If you are working with only primitive types then that is all you need to know and you can stop reading here. However if your module needs to work with a struct then it gets just a little more complicated.
Problematic example header b.h:
typedef struct Obj {
int data;
}*Obj;
Obj make(void);
void work(Obj o);
Your module wants to pass objects in and out. The problem here is, that internals are leaked to other modules that depend on this header. If the representation is changed to float data
then all using modules have to recompile. One way to fix this is to only use void*
. That is how many programs do it. However that is cumbersome because every function getting the void*
as argument has to cast it to Obj
. Another way is to do this:
Header c.h:
typedef struct Obj*Obj;
Obj make(void);
void work(Obj);
Implementation c.c:
#include "c.h"
typedef struct Obj {
int data;
}*Obj;
The reason why this works is, that Obj is a pointer (as opposed to a struct by value/copy). Other modules that depend on this module only need to know that a pointer is being passed in and out, not what it points to.
gcc -c filename.c
for every c-file. Each of them generates an .o-file (module). At the end you call gcc *.o
which links them all together. If you have changed only b.c
then only it needs to be recompiled (b.o
) and you then link again. (the unchanged c-files dont need to be compiled again) –
Mariko Obj make();
tells the compiler that the function takes an unspecified number of arguments subject to default argument promotion; the correct prototype would read Obj make(void);
–
Montserrat echo "void foo(); int main() { foo(42); }" | clang -fsyntax-only -Weverything -xc -
will parse without complaint; add the void
, and it won't –
Montserrat .h
files. I have been doing this kind of function implementation/declaration for a time, but did not think it was a bad architectural idea. Thanks for the tip! –
Reticulum You must read something about OOP with non OOL such like http://www.cs.rit.edu/~ats/books/ooc.pdf. But, doing such you will never have strong OOP typing.
Please do yourself a favor and read C Interfaces and Implementations: Techniques for Creating Reusable Software
Here is a repository of mine that holds some libs written in C using the pattern of interfaces & implementation described in the book.
© 2022 - 2024 — McMap. All rights reserved.