How to use multiple source and header files

24,115

Solution 1

You may be having problems because you haven't yet got a strong reason to split things up. A good reason would help you identify which parts belong together, and which parts are separate. So start with a simpler approach.

Split the program into three files, main.c, which contains main(), node.h, the header which ensures declarations are common across all the program, and hence is understood by the compiler, and node.c, the functions which manipulate the NODE structure.

Put the typedef ... NODE; and all the declarations of functions which manipulate NODE into one the node.h header file. So you could merge the existing header files into one, and call it node.h.

As Joop Eggen recommends, put #ifndef _NODE_H_ ... #endif around node.h contents to protect it against accidentally being #included twice.

Test that file is correct with a minimal main.c file containing:

#include "node.h"

int main() { return 0; }

and compile it. That should give no compilation errors. If it contains errors the fault is in the header file.

Put the functions that manipulate NODE, into a file called node.c, which will initially be:

#include "node.h"

compile and link that with main.c (gcc main.c node.c), and there should be no errors.

Build up the program is stages, adding code to the main.c file, the node.c file, and add declarations of functions in the node.c file into node.h. Add small amounts of code, and compile frequently (with warnings switched on, e.g. gcc -Wall main.c node.c) and test to make sure it is doing what you expect.

The program will eventually be complete.

Solution 2

I recommend looking at What are extern variables in C?.

You can include system headers such as <stdio.h> without having to worry about whether there are other headers needed to make use of its services. You should design your own headers in the same way. You should also prevent errors if your file is included multiple times (whether accidentally or deliberately).

You have:

  • b_tree_ds.h

    typedef struct node {
        struct node* left;
        struct node* right;
        int key;    // contains value
    } NODE;
    

    Up to a point, this is fine; you just need to wrap it in header guards so reinclusion does no damage.

    #ifndef B_TREE_DS_H_INCLUDED
    #define B_TREE_DS_H_INCLUDED
    
    typedef struct node {
        struct node* left;
        struct node* right;
        int key;    // contains value
    } NODE;
    
    #endif /* B_TREE_DS_H_INCLUDED */
    

    You note:

    When I try adding extern as in typedef extern struct node it gives a error of multiple storage class but if I miss it, I get error for multiple definitions.

    Syntactically, extern, static, auto, register and typedef are all storage classes, and you can only have one storage class in a given declaration. That's why you get the multiple storage class error. The 'multiple definition' error will continue to be a problem until C2011 is widespread, and the header guards prevent that from being a problem. I think header guards will remain valuable even after C2011 is widely available.

  • traverse.h

    void traverse_print (NODE* p);
    

    As it stands, you can't simply write #include "traverse.h" to make use of its facilities. This is something to be avoided whenever possible. (See: Self-sufficent header files in C and C++, What is a good reference documenting patterns of use of h files in C, and Should I use #include in headers.) Therefore, this should include b_tree_ds.h:

    #ifndef TRAVERSE_H_INCLUDED
    #define TRAVERSE_H_INCLUDED
    
    #include "b_tree_ds.h"
    
    extern void traverse_print(NODE *p);
    
    #endif /* TRAVERSE_H_INCLUDED */
    

    You could omit the header include guards on this header (assuming that b_tree_ds.h is self-protects), but it is simpler to be self-consistent in all headers.

    There is one other possible technique that could be mentioned:

    #ifndef TRAVERSE_H_INCLUDED
    #define TRAVERSE_H_INCLUDED
    
    typedef struct node NODE;
    
    extern void traverse_print(NODE *p);
    
    #endif /* TRAVERSE_H_INCLUDED */
    

    This makes NODE into an opaque type; the user of the header traverse.h knows nothing about what's in a NODE. There are coordination issues to resolve that make this a less commonly used technique.

With these changes to the headers, then:

  • traverse.c only needs to include traverse.h (and should arguably include it before any other header to provide an automatic test of self-containment), but
  • If traverse.c includes both headers, there are no problems, regardless of the order in which they are included (and it doesn't matter if the repetition is direct or indirect).
  • Your main.c can include just traverse.h as shown and will be OK. With the original code, because main.c only included traverse.h and traverse.h did not include b_tree_ds.h, the code would not compile properly.
Share:
24,115
simar
Author by

simar

Updated on March 18, 2020

Comments

  • simar
    simar about 4 years

    I recently learnt how can we use multiple source files with header files to make code portable and hierarchical. In order to do so, I tried to create my tree program using this principle. Here are my files

    b_tree_ds.h - This will contain a declaration of datastructure of node of a tree, which can be called to various functions implementing different functionality of the tree (which may be in different source files)

    typedef struct node {
        struct node* left;
        struct node* right;
        int key;    // contains value
    }NODE;
    

    When i try adding a extern as in typedef extern struct node it gives a error of multiple storage class but if I miss it, I get error for multiple definitions.

    Here are my other source files

    traverse.h - contains declaration of traverse function

    void traverse_print (NODE* p);
    

    Here also I get error for unknown identifier NODE

    traverse.c - contains definition to that function

    #include <stdio.h>
    #include "b_tree_ds.h"
    #include "traverse.h"
    
    void traverse_print(NODE* p)
    {
        if(p->left != NULL)
        {
            traverse_print(p->left);
        }
    
        if (p->right != NULL)
        {
            traverse_print(p->right);
        }
    
        printf ("\n%d",p->key);
    }
    

    Finally main.c

    #include <stdio.h>
    #include "traverse.h"
    
    void main()
    {
        // input
        NODE p;
    
        printf("\nInput the tree");
        input_tree (&p);
    
        printf("\n\nThe tree is traversing ...\n")
        traverse_print(&p);
    }
    
    void input_tree (NODE *p)
    {
        int in;
        int c;
        NODE *temp;
    
        printf("\n Enter the key value for p: ");
        scanf("%d", &in);
        p->key  =in;
        printf ("\n\nIn relation to node with value %d",in);
        printf ("Does it have left child (Y/N): ")
        if ((c = getchar()) == Y);
        {
            //assign new memory to it.
            temp = (NODE *)malloc(sizeof(NODE));
            input_tree(temp);
        }
        printf ("\n\nIn relation to node with value %d",p->key);
    
        printf ("\nDoes it have right child (Y/N): ")
        if ((c = getchar()) == Y);
        {
            //assign new memory to it.
            temp = (NODE *)malloc(sizeof(NODE));
            input_tree(temp);
        }
    }
    

    This is my first attempt to such practice, please suggest is the structuring of my program good or should I try something else.