Categories
c c++ extern-c linkage name-mangling

What is the effect of extern “C” in C++?

1989

What exactly does putting extern "C" into C++ code do?

For example:

extern "C" {
   void foo();
}

1

1851

extern "C" makes a function-name in C++ have C linkage (compiler does not mangle the name) so that client C code can link to (use) your function using a C compatible header file that contains just the declaration of your function. Your function definition is contained in a binary format (that was compiled by your C++ compiler) that the client C linker will then link to using the C name.

Since C++ has overloading of function names and C does not, the C++ compiler cannot just use the function name as a unique id to link to, so it mangles the name by adding information about the arguments. A C compiler does not need to mangle the name since you can not overload function names in C. When you state that a function has extern "C" linkage in C++, the C++ compiler does not add argument/parameter type information to the name used for linkage.

Just so you know, you can specify extern "C" linkage to each individual declaration/definition explicitly or use a block to group a sequence of declarations/definitions to have a certain linkage:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

If you care about the technicalities, they are listed in section 7.5 of the C++03 standard, here is a brief summary (with emphasis on extern "C"):

  • extern "C" is a linkage-specification
  • Every compiler is required to provide “C” linkage
  • A linkage specification shall occur only in namespace scope
  • All function types, function names and variable names have a language linkage See Richard’s Comment: Only function names and variable names with external linkage have a language linkage
  • Two function types with distinct language linkages are distinct types even if otherwise identical
  • Linkage specs nest, inner one determines the final linkage
  • extern "C" is ignored for class members
  • At most one function with a particular name can have “C” linkage (regardless of namespace)
  • extern "C" forces a function to have external linkage (cannot make it static) See Richard’s comment: static inside extern "C" is valid; an entity so declared has internal linkage, and so does not have a language linkage
  • Linkage from C++ to objects defined in other languages and to objects defined in C++ from other languages is implementation-defined and language-dependent. Only where the object layout strategies of two language implementations are similar enough can such linkage be achieved

19

  • 32

    C compiler does not use mangling which c++’s does. So if you want call a c interface from a c++ program, you have to clearly declared that the c interface as “extern c”.

    – Sam Liao

    Jun 25, 2009 at 2:28

  • 68

    @Faisal: do not try to link code built with different C++ compilers, even if the cross-references are all ‘extern “C”‘. There are often differences between the layouts of classes, or the mechanisms used to handle exceptions, or the mechanisms used to ensure variables are initialized before use, or other such differences, plus you might need two separate C++ run-time support libraries (one for each compiler).

    Jun 25, 2009 at 3:24

  • 16

    ‘extern “C” forces a function to have external linkage (cannot make it static)’ is incorrect. ‘static’ inside ‘extern “C”‘ is valid; an entity so declared has internal linkage, and so does not have a language linkage.

    Feb 14, 2013 at 4:06

  • 19

    ‘all function types, function names and variable names have a language linkage’ is also incorrect. Only function names and variable names with external linkage have a language linkage.

    Feb 14, 2013 at 4:07

  • 17

    Note that extern "C" { int i; } is a definition. This may not be what you intended, next to the non-definition of void g(char);. To make it a non-definition, you would need extern "C" { extern int i; }. On the other hand, the one-declaration syntax without braces does make the declaration a non-definition: extern "C" int i; is the same as extern "C" { extern int i; }

    – aschepler

    Jul 1, 2014 at 16:39


405

Just wanted to add a bit of info, since I haven’t seen it posted yet.

You’ll very often see code in C headers like so:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

What this accomplishes is that it allows you to use that C header file with your C++ code, because the macro “__cplusplus” will be defined. But you can also still use it with your legacy C code, where the macro is NOT defined, so it won’t see the uniquely C++ construct.

Although, I have also seen C++ code such as:

extern "C" {
#include "legacy_C_header.h"
}

which I imagine accomplishes much the same thing.

Not sure which way is better, but I have seen both.

13

  • 12

    There is a distinct difference. In case of the former, if you compile this file with normal gcc compiler it will generate an object where the function name is not mangled. If you then link C and C++ objects with the linker it will NOT find the functions. You will need to include those “legacy header” files with the extern keyword as in your second code block.

    Apr 12, 2013 at 14:00

  • 13

    @Anne: The C++ compiler will look for unmangled names also, because it saw extern "C" in the header). It works great, used this technique many times.

    – Ben Voigt

    Jun 27, 2014 at 5:34

  • 30

    @Anne: That’s not right, the first one is fine as well. It’s ignored by the C compiler, and has the same effect as the second in C++. The compiler couldn’t care less whether it encounters extern "C" before or after it includes the header. By the time it reaches the compiler, it’s just one long stream of preprocessed text anyway.

    – Ben Voigt

    Jun 30, 2014 at 15:54


  • 9

    @Anne, no, I think you’ve been affected by some other error in the source, because what you are describing is wrong. No version of g++ got this wrong, for any target, at any time in the last 17 years at least. The whole point of the first example is that it doesn’t matter whether you use a C or C++ compiler, no name mangling will be done for the names in the extern "C" block.

    Jan 19, 2016 at 20:45

  • 9

    “which one is better” – for sure, the first variant is better: It allows including the header directly, whithout any further requirements, both in C and C++ code. The second approach is a workaround for C headers the author forgot the C++ guards (no problem, though, if these are added afterwards, nested extern “C” declarations are accepteded…).

    – Aconcagua

    Aug 9, 2017 at 9:23

385

Decompile a g++ generated binary to see what is going on

main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Compile and disassemble the generated ELF output:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

The output contains:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
     9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
    10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

Interpretation

We see that:

  • ef and eg were stored in symbols with the same name as in the code

  • the other symbols were mangled. Let’s unmangle them:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Conclusion: both of the following symbol types were not mangled:

  • defined
  • declared but undefined (Ndx = UND), to be provided at link or run time from another object file

So you will need extern "C" both when calling:

  • C from C++: tell g++ to expect unmangled symbols produced by gcc
  • C++ from C: tell g++ to generate unmangled symbols for gcc to use

Things that do not work in extern C

It becomes obvious that any C++ feature that requires name mangling will not work inside extern C:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

Minimal runnable C from C++ example

For the sake of completeness and for the newbs out there, see also: How to use C source files in a C++ project?

Calling C from C++ is pretty easy: each C function only has one possible non-mangled symbol, so no extra work is required.

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

#include "c.h"

int f(void) { return 1; }

Run:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

Without extern "C" the link fails with:

main.cpp:6: undefined reference to `f()'

because g++ expects to find a mangled f, which gcc did not produce.

Example on GitHub.

Minimal runnable C++ from C example

Calling C++ from C is a bit harder: we have to manually create non-mangled versions of each function we want to expose.

Here we illustrate how to expose C++ function overloads to C.

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

Run:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

Without extern "C" it fails with:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

because g++ generated mangled symbols which gcc cannot find.

Example on GitHub.

Where is the extern "c" when I include C headers from C++?

Tested in Ubuntu 18.04.

6

  • 44

    Best answer since you 1) explicitly mention that extern "C" { helps you call unmangled C functions from within C++ programs, as well as unmangled C++ functions from within C programs, which other answers don’t make so obvious, and 2) because you show distinct examples of each. Thanks!

    Oct 25, 2018 at 17:51

  • 1

    I’m wondering about C headers like unistd.h, sys/stat.h and sys.types.h. They don’t seem to put the “‘C'” after the “extern”. Using them from C++ code still seems to be unproblematic. Is the reason, that these are pure headers without an implementation file?

    – paleonix

    Oct 3, 2020 at 13:08


  • 1

    @Paul they seem to enable extern C with the macro __BEGIN_DECLS: stackoverflow.com/questions/8087438/… I observe what is mentioned in that answer on Ubuntu 20.04 for unistd.h. For cstdio however, it might be relying on the #pragma GCC system_header: gcc.gnu.org/onlinedocs/cpp/System-Headers.html

    Oct 3, 2020 at 15:00


  • Thanks! Weirdly that question didn’t show up when I searched and now id did when I searched for that specific Macro… I guess it’s good for it to be linked here. As __BEGIN_DECLS is defined in sys/cdefs.h but this isn’t included in either of unistd.h, sys/stat.h and sys/types.h, I guess sys/cdefs.h is just included by the preprocessor by default?

    – paleonix

    Oct 4, 2020 at 0:40

  • @Paul no worries, we all live and die by the wims of the Google God. It gets included via #include <features.h>.

    Oct 4, 2020 at 7:15