cgccfile-ioc-preprocessorname-clash

Am I Utilizing the Preprocessor the Wrong Way?


Please Note: This is NOT homework. The program is not complete and does not fully function, but should, at the very least, compile.

I am in the midst of a self-taught process using the C Primer Plus Book (In short I'm new to C). I've nearly completed reading the whole book and have been working through the exercises for each chapter and from time to time I have gone off on a tangent. This is one of those times. I've run in to a peculiar issue and I'm pretty sure is related to the preprocessor directives.

I am using MinGW (gcc for windows) and it reports:

The error that gcc reports is:

nanfunct.c: multiple definition of 'keywords'
nanite.c: first defined here
etc... etc... more errors...

I'm pretty sure that this is caused by the inclusion of multiple header files, but more importantly the header file that I created and then included is causing this issue.

The issue seems to be linked to pointer-to-char-arrays (or string based arrays) which are duplicated at compile time even though i say to define it only if has not been predefined already.

for example:

#ifndef MENU_OPTIONS
#   define MENU_OPTIONS ON
#   if MENU_OPTIONS == ON
        ...some code here...

        char * keywords[] = {
            "copy", "help", "line",
            "quit", "read", "write"
        };

        char * keyletters[] = {
            "c", "h", "l",
            "q", "r", "w"
        };
#   endif
#endif

I am using three files:

nanite.c -> source file for main()
nanfunct.c -> source file for functions
nanproto.h -> header file for nanite.c and nanfunct.c

Inside of nanite.c and nanfunct.c I #include nanproto.h

source files posted on pastebin:
nanproto.h -> Header File for nanite.c and nanfunct.c
nanite.c & nanfunct.c -> source files

Why is this happening? I thought that #ifndef was supposed to keep things like this from happening?


Solution

  • You are misunderstanding what the preprocessor does, or how C source files are compiled and linked.

    Each source file is preprocessed separately. So, after preprocessing, nanfunct.c contains definitions of keywords and keyletters. The preprocessed source is then compiled into the object file nanfunct.o.

    After preprocessing, nanite.c also contains definitions of keywords and keyletters. This preprocessed source is compiled to produce the object file nanite.o.

    The linker then tries to combine nanfunct.o and nanite.o. It finds that there is more than one definition of keywords and keyletters, so it displays an error message and aborts.

    If you want something to be available in multiple source files, the usual pattern is to put the declaration in a header file, and the definition in one source file.

    Move this:

    char * keywords[] = {
            "copy", "help", "line",
            "quit", "read", "write"
    };
    
    char * keyletters[] = {
            "c", "h", "l",
            "q", "r", "w"
    };
    

    into either nanite.c or nanfunct.c (not both). Add these declarations to nanite.h:

    extern char * keywords[];
    extern char * keyletters[];
    

    That way, the definitions are only included in one object file.

    Note that this only applies to global variables and functions. It does not apply to structs, unions, enums, or typedefs, as these aren't included in object files.