Solving function name conflicts between C and C++ files

This tutorial shows how to resolve the “undefined reference” problem when using both C and C++ code in one project. We will create a basic project, show how to reproduce the “undefined reference” error for a function that is defined in a C file, explain how it happens and show how to solve it in a universal way.

  1. Start Visual Studio and create a project using any of the VisualGDB project wizards. We will show it based on the Embedded wizard, but the same steps will also work for the Linux, Android and Windows wizards:01-externcname
  2. Add a new file called add.h to your project and add the following code there:
    #pragma once
    int add(int a, int b);
  3. Create a new file called add.c (not add.cpp) and provide an implementation for that function. You can use the ‘Create implementation’ command provided by the Clang IntelliSense for simplicity:02-header
  4. Build the project to ensure there are no syntax errors:03-source
  5. Now include the <add.h> file from your main C++ source (if your project has a .c source, change its extension to .cpp). If you build your project now, you will see the following error:
    undefined reference to `add(int, int)'

    04-undefined

  6. The error happens at the linking stage, i.e. after each of the source files was compiled to a .o file but before all the .o files are merged into the final executable. To see why exactly this happens we will use the “nm” tool from the GCC toolchain. The tool displays a list of symbols in an object file. Run it on the object file that defines the add() function and the one that uses it:05-namesNote that the add.o file provides a symbol called “add” while the LEDBlink.o file expects a symbol called “_Z3addii” (‘U’ before the names means that the symbol is undefined in this object file and must be defined elsewhere). The difference in the names causes the “undefined reference” error shown above.
  7. The “_Z3addii” is the internal GCC name for ‘function add that takes arguments of type int and int). This name transformation is done automatically to support function overloading. E.g. you can declare another function called ‘add’ that will accept 3 arguments and the code will compile successfully (and stop at linking as before):06-add2
  8. Looking into the new .o file will show that it now expects 2 functions: _Z3addii and _Z3addiii. So the name transformation is needed for the linker to distinguish between those functions:07-sym2
  9. The easiest way to fix this error is to put an extern “C” block around the #include<> statement:08-externc
  10. When a C++ compiler encounters a function defined inside an extern “C” block, it disables the name transformation for it, so it will generate the same name for it as the C compiler would:09-externsym
  11. As a side effect of this, you will not be able to overload the function anymore. Any attempt to declare another function called ‘add’ will result in a compilation error, as the compiler cannot distinguish between those functions without using name transformation that was disabled by extern “C”: 10-conflict
  12. In order to avoid putting extern “C” around the included header files, it is recommended to put this block directly inside them. However putting it unconditionally will prevent the C files from compiling properly:11-externerr
  13. The solution to this is very simple. Use the __cplusplus macro to detect whether the header file is parsed by a C or C++ compiler and only include extern “C” for C++ files:
    #pragma once
     
    #ifdef __cplusplus
    extern "C"
    {
    #endif
        
        //place your functions here
        
    #ifdef __cplusplus    
    }
    #endif

    12-ifdef

  14. Same problem will happen if a .c file tries to call a function defined in a .cpp file without declaring it with extern “C”. If this happens, ensure you have extern “C” in your header file and that the header file is actually included by the .cpp file before the function is defined.