Search code examples
c++cmakefilec-preprocessorconditional-compilation

Conditional compilation confusion and failure


I want to compile different files with a common *.c file. Like I want to compile A.c common.c xor B.c common.c but I can't figure out how to achieve that.

Can you please tell me how do I make common.c use different headers without using my text editor to change the headers list every time I want to compile

So let's say I have 3 files: A.c, B.c and common.c.

A.h and B.h define an enum enum {Mon, Tues, Wed...} in different ways. And that enum is used in common.c. So I can't just do in common.c:

#include "A.h"
#include "B.h"

What I thought of doing is to use preprocessor directives:

In common.h

#define A 1
#define B 2

In A.c

#define COMPILE_HEADER A

And in common.c

#if COMPILE_HEADER == A 
#include A.h 
#end

This doesn't work, of course, because the compiler didn't visit the A.c file to find #define COMPILE_HEADER A

So can you please tell me how do I make common.c use different headers without using my text editor to change the headers list every time I want to compile?


Solution

  • It's pretty complicated to explain, but I'll give it a try.

    Explaination

    The compiler, for example gcc, gets the input files provided, includes (literally just copy-pastes) the header files into their respective places (where the #include directive is located) in the *.c file, then compiles the file to the object (*.o) file. No executable is created. Here comes the linker, which is included in gcc. It takes the *.o files and links them into one executable.

    The problem is, that the files are compiled independently, and then linked together. I mean, that predefinition like int func(int param); is like saying to compiler "Hey man, don't worry about any usage of func in the code, the linker will care". Compiler then just saves this usage as external symbol in the corresponding *.o file, and when linker is doing his job, he firstly finds the location of the symbol definition (the function implementation) and then just points to it whenever the func is called.

    Try to include function definition in header file, then include it in 2 or more files from same project (compiled/linked together). Compiler will say it's ok, since the code is correct and the generated code is valid. Then, the linker will try to link it into one executable and he would have to decide which version of the same name-param function should it link to. Since most developer tools are not really good at making the right choices, he will just yell at you saying "hey man, you gave me two definitions of same function, what to do now?". This results with an error like this:

    obj\Release\b.o:b.cpp:(.text+0x0): multiple definition of 'func(int)'
    obj\Release\a.o:a.cpp:(.text+0xc): first defined here
    

    What about having two main in one project?

    obj\Release\b.o:b.cpp:(.text.startup+0x0): multiple definition of 'main'
    obj\Release\a.o:a.cpp:(.text.startup+0x0): first defined here
    

    Both files compile, but they cannot be linked together.

    The header files are meant to contain class definitions and function predefinitions to allow you to write them only once, and then share between all files that want to use them. You can always just type class definitions for each file separately (as long as they stay same) and use them just like you would use them in .h file, same applies for function predefinitions.

    There comes your problem. You have to compile only one file, not include only one. You can do this by using the preprocessor trick, but I wouldn't reccomend as solution, since it can be solved much easier (I'll tell you how in a moment).

    TL;DR; (The actual answer without explaination)

    You can #ifdef / #ifndef both files, then in another *.c define (or not define) some value. For example:

    A.cpp

    #include "t.h"
    
    #ifdef USE_A
    int func(int a)
    {
        return a + 5;
    }
    #endif
    

    B.cpp

    #include "t.h"
    
    #ifndef USE_A
    int func(int a)
    {
        return a * 10;
    }
    #endif
    

    T.h

    #ifndef T_H
    #define T_H
    
    //Or comment to use B
    #define USE_A
    
    int func(int a);
    
    #endif // T_H
    

    main.cpp

    #include <iostream>
    #include "t.h"
    
    using namespace std;
    
    int main()
    {
        cout << func(3);
    }
    

    Notice that the #ifdef/#ifndef are after the #include, so the preprocessor knows which one to compile. Also, it's not possible (at least I can't think of any way) to make the definition in any .c file, because they are compiled separately (as described above). You could use the -D switch, but that involves messing with build configurations in environments and if you want to do that it's better to try the second solution presented bellow.

    Better answer

    You should choose one file to compile with each version. It hardly depends on your needs, but basically instead of using g++ main.cpp a.cpp b.cpp you should compile either a.cpp or b.cpp. If you are using integrated environment like Visual Studio or Code Blocks, the configuration managers allow you to decide which file to include. I mean, those Debug/Release dropdowns can contain your own entries, which will customize your project. Then, switching between the A.cpp and B.cpp is just a question of choosing appropiate option in the always-visible bar in your environment.

    If you want a more detailed tutorial on how to manage configurations on Code::Blocks or Visual Studio create appropiate question on stackoverflow and it will be answered in no time :)