cfile

Decomposition of a C big file in many other files


I plan to create a zip folder in which to put some files with the extension .c and .h (for C programs) and .txt (for README for example or other) in order to create a C program template. Consider the following prototype file with many libraries and many functions called in main, called for example myprogram_bigfile.c

//(FROM HERE)//
/*Author(s): ...
Title: ...
Description(s): ...
Copyright and License(s): ...
*/

/*Start List of libraries*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include "gen.h"
#include "defs.h"
#include "readlua.h"
#include "logic.h"
#include "state.h"
#include "session.h"
#include "files.h"
#include "play.h"
#include "io.h"
// and many more libraries...
/*End List of libraries*/
//(TO HERE)//

/*Start Function List*/
//Many functions...
/*End Function List*/

int main()
{
...
return something;
}

I ask you if it is possible to break down myprogram_bigfile.c into:

  1. a file (for example called myprogram_descriptions_and_libraries) that includes only the part that goes from //(FROM HERE)// to //(TO HERE)//;
  2. another file (for example called myprogram_functions) that includes only the part of the list of functions
  3. and finally, last but not least, a source file called for example myprogram_main.c that calls the above files.

Thank you very much in advance.


Solution

  • So, let's go there - the canonical way of having larger projects is to have each C file built into a (non standalone) "object" file - in modern systems these will have the .o extension. (Back in my time, handcrafting some C in DOS these were .objs)

    I will be using gcc for the example - other compilers should be analogue: when you have a single file project, with the main function and everything else in the same file (and I can tell you colleges in my country to teach this and nothing else, getting a lot of quiters), you can call gcc like:

    gcc myprogam.c -o myprogram
    

    (I guess for Windows, once GCC is installed it is the same, just the executable must have the .exe extension: gcc myprogram.c -o myprogram.exe )

    Now let's settle for two different source files:

    project.c

    # include <stdio.h>
    #include "project.h"
    
    
    int main() {
        int value;
        value = other_func();
        printf("The value is %d\n", value);
        return 0;
    }
    

    And

    somelib.c

    int other_func() {
            return 23;
    }
    

    This could have a single .h file that would expose other_func -but as projects gets larger, the practice is to: each .c file have a corresponding ".h" file, and the final project have a .h file which includes all the others.

    By making hierarchical .h files this way, you can have all the functions in your project with just one or a few includes, ready to use.

    somelib.h

    int other_func();  // this is the only thing the main file needs to see about the library
    

    project.h

    #include "somelib.h"
    

    And with these files in place, you could manually build the object files - not to executable, with the -c GCC flag:

    $ gcc somelib.c -c somelib.o
    $ gcc project.h -c somelib.o
    

    Due to the .h file being included into project.c it won't complain about not knowing about other_function due to the forward declarations in the included header files.

    Then the final step links the object files together:

    $ gcc project.o somelib.o -o project
    

    Notice the -o switch telling it should generate a standalone executable.

    ANd that is it.

    You can see this gets complex fast -so back in the 1970s people came around with Makefiles - which can still be used for simple projects. It is really cryptical (in my opinion) in today's world of automations and very high level language - for these four files, a Makefile that works is this:

    Makefile

    TARGET = project
    
    SRCS = project.c somelib.c
    
    OBJS = $(SRCS:.c=.o)
    
    HEADERS = project.h somelib.h
    
    all: $(TARGET)
    
    $(TARGET): $(OBJS)
            gcc $(OBJS) -o $(TARGET)
    %.o: %.c $(HEADERS)
            gcc -c $< -o $@
    
    clean:
            rm  $(OBJS) $(TARGET)
    
    

    The Makefile has really to be named literally "Makefile" - and once it is there, you type make in the same directory, to have everything built - and moreover: with dependencies checked, so that upon changing one file, it will rebuild only the files which depend on that one.

    (sorry, don't ask me more about the Makefile- I just modified something I got on the internet) - this can be fine up to a few tens of files, and a fixed compiler and build stack

    Over the 90s as projects got more complex there came the "GNU automake" - a project which will generate the Makefile based on config options you pass to the autogen.sh script - and it goes from testing which compilers are available in the system, and generating the proper changes into the makefile (it also needs a template for the makefile).

    More modern workflows use frameworks called and - those are known to be much more maintainable and friendly - both project for authors as for users who will occasionally build a project. You could get some tutorials for one of those, and maybe skip the Makefile part altogether.

    -- And finally - that is all there is to it:
    cut your C files in proper subfolders, with a few functions grouped by domain in each file, and have a simple main file which just has the entrypoint and calls upon the others.