Understanding extern “C” in C again

Original link: https://blog.vvzero.com/2023/07/03/learn-extern-c-in-cpp-again/

This article is part of the “Jade Attack Project”, translated from the answer of Ciro Santilli in https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c

Understand the role of extern “C” through disassembly

main.cpp

 1
2
3
4
5
6
7
8
9
10
 void f () {}
void g () ;

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

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

Compile the above code into a binary in ELF format, and then disassemble:

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

Extract part of the output:

 1
2
3
4
5
6
 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

visible:

  • The names of ef and eg in the symbol table are the same as those in the original code
  • Other symbols have been modified, we can use the c++filt tool to restore its original appearance:
 1
2
3
4
5
6
 $ c++filt_Z1fv
f()
$ c++filt_Z1hv
h()
$ c++filt_Z1gv
g()

So, we need to use extern "C" in the following two cases:

  • Calling C code in C++: tells g++ that undecorated symbol names generated by gcc may be encountered
  • Calling C++ code in C: Let g++ generate undecorated symbol names for gcc to call

Certain codes that cannot be used in extern “C”

Obviously, any language feature that requires C++ name decoration cannot be written in extern "C" :

 1
2
3
4
5
6
7
8
9
10
 extern "C" {
// function overloading
// Error: Conflicting declaration of f
void f () ;
void f ( int i) ;

// template
// error: template cannot be used for linking in C
template < class C > void f (C i) { }
}

A minimal runnable code sample calling C code from C++

Calling C code from C++ is simple: every C function has just an undecorated symbol name, so no extra work is required.

main.cpp

 1
2
3
4
5
6
7
 # include <cassert>

# include "ch"

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

ch

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
 #ifndef C_H
#define C_H

/* Here, ifdef allows this header file to be used in both C++ projects and C projects,
* Because there is no definition of extern "C" in the C standard*/
#ifdef __cplusplus
extern "C" {
#endif
int f () ;
#ifdef __cplusplus
}
#endif

#endif

cc

 1
2
3
 # include "ch"

int f ( void ) { return 1 ; }

run:

 1
2
3
4
 g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o co -std=c89 cc
g++ -o main.out main.o co
./main.out

If there is no extern "C" , the linker will report an error:

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

Because g++ will look for a modified f , but gcc will not compile the modified symbol name.

A minimal runnable code sample calling C++ code from C

Calling C++ code from C is a little more difficult: we need to manually manage all the functional interfaces exposed to C and make them undecorated at compile time.

code show as below:

main.c

 1
2
3
4
5
6
7
8
9
 # include <assert.h>

# include "cpp.h"

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

cpp.h

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 #ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// These two overloaded functions cannot be exposed to the C compilation group, otherwise an error will be reported
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

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 # 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:

 1
2
3
4
 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

If extern "C" is not added, an error will be reported:

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

Because g++ generates decorated symbol names, but gcc can’t understand them.

Where is the extern “C” when I include the standard library C headers in C++?

This article is transferred from: https://blog.vvzero.com/2023/07/03/learn-extern-c-in-cpp-again/
This site is only for collection, and the copyright belongs to the original author.