Search code examples
cstructabstract-data-type

C Abstract data type pointer - Program modularity


If some module depends from some other sub-module, either including it in its header or in its code file, And what if modules, besides contributing to the main program, are also mutually dependent?

The figure below schematically illustrates a situation in which module1.h/.c requires a sub-module module4.h, and module2 requires module3.

Example

Each header file has their own typedef struct exemple:

typedef struct list * List;

and the source file related to the header files implement the struct like :

struct list {
    unsigned length;
    char * value;
};

I got another struct in a header file:

typedef enum {START, END, MIDDLE, COMMENTS, CONDITIONS} TypeListBal;
typedef struct bal * Bal;

and source file :

struct bal {
    TypeListBal type;
    List name;              // Name of the bal
    List attributes[];          // Array of type struct List
};

I've been reading a lot tonight and I'm not quite sure about one thing. If I'm just including the header file in the source file that I need in my case, I'm including list.h in bal.c cause my bal.c got a struct definition which as member of type List.

To make it work, do I have to put specific #INCLUDE in all my modules or will the job be done at the compilation?

I mean in make makefile, to have my bal.o object, it will look like:

bal.o: list.o bal.c bal.h
       $(CC)   -g -W -Wall -c  list.o bal.c bal.h

So as soon the makefile try to compile bal.o, the compiler will see all the dependencies, and will be able to compile everything.

That's how I did understand but when I try this, I get:

error: dereferencing pointer to incomplete type

I'm guessing it's because in my bal.c file, I'm declaring variable of type struct List and the compiler doesn't have any ideas about struct List cause the definition of struct list is in the list.c file.

So the question again is: How do I connect everything to make it work?


Solution

  • Your header file is broken. This is an incomplete type:

    typedef struct bal * Bal;
    

    It needs the struct declaration to make it complete. That ought to be in the header file, too. If in C code you say *Bal without the complete type, the compiler gives up because it doesn't have any information about what this expression means, i.e. the fields of the struct.

    There should be absolutely no problem with the dependency graph you're showing. You want to use guards to ensure header files are included only once. Then you can include one header from another, and the C preprocessor will "just work" as long as there are no cyclic dependencies. In your case, the header for e.g. module 4 would look like:

    // module4.h
    #ifndef MODULE_4_H_INCLUDED
    #define MODULE_4_H_INCLUDED
    
    #include "module1.h"
    
    // all public type declarations for module 4 types
    
    #endif // MODULE_4_H_INCLUDED
    

    Make all the headers (including module1.h) similar, with their own guard definitions. Then the module 4 code:

    // module4.c
    #include "module4.h"
    
    // Use module 4 types.
    

    In general, if you need types given by a header file in some .c file, include it even if it would be included by dependencies between headers. The guards will generally ensure no redundancy.

    Additionally, your idea of make dependencies is incorrect. E.g. to compile module4.c successfully to obtain module4.o, you need (of course) module4.c, module4.h, and module1.h. Those are the build dependencies. So the rule will be:

    module4.o : module4.c module4.h module1.h
            $(CC) -g -W -Wall -c module4.c
    

    But you should not write specific rules like this. Make has built-ins and general rules that are simpler to use. And you can use gcc itself to generate the header file dependencies rather than tracking them manually (which can lead to all kinds of disaster). See the -M option.