0%

extern c

Guide

extern c

相比C语言,C++支持函数重载、类、模版等各种特性,如果C++的符号修饰仍按照C那样直接使用对应名称,虽然可读性很好,却不可避免的会出现各种错乱,于是C++符号修饰别名(mangled name)被设计出来, 用来解决上述问题,然而修饰规则取决于编译器实现,没有统一标准,比如gcc、msvc就各有一套,但好在都提供了相应接口进行解析(demangle),这里有个网站demangler可以在线解析

c++ function: managled names
c function: unmangled names
extern c告诉c++编译器将f_int,f_float视为c函数,使用 unmangled names(修饰名称)

managled/unmangled names

a.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 with GCC 4.8 Linux ELF output:

g++ -c a.cpp

Decompile the symbol table:

readelf -s a.o

output

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

managled names: _Z1fv,_Z1hv,_Z1gv for f,g,h
unmangled names: ef,eg

unmanagle them

c++filt _Z1fv
f()

c++filt _Z1hv
h()

c++filt _Z1gv
g()

extern C中错误用法

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

error.cpp

1
2
3
4
5
6
7
8
9
10
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) { }
}

compile and generate errors

g++ -c error.cpp 
error.cpp:5:17: error: conflicting declaration of C function ‘void f(int)’
     void f(int i);
                 ^
error.cpp:4:10: note: previous declaration ‘void f()’
     void f();
          ^
error.cpp:9:5: error: template with C linkage
     template <class C> void f(C i) { }
     ^

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 gcc to use unmangled symbols produced by g++

cpp中使用c方法

code

main.cpp

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

#include "c.h"

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

c.h

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

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif

int f();

#ifdef __cplusplus
}
#endif

#endif

c.c

1
2
3
#include "c.h"

int f(void) { return 1; }

OK

compile main.cpp

g++ -c -o main.o -std=c++98 main.cpp

readelf -s main.o
10: 0000000000000000    46 FUNC    GLOBAL DEFAULT    1 main
11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND f
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __assert_fail

使用c++编译器,extern c告诉c++编译器f是一个c函数,使用unmanagled name f

compile c.c

gcc -c -o c.o -std=c89 c.c

readelf -s c.o 
8: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 f

使用c编译器,对于c函数生成unmanagled name f

link main.o and c.o

g++ -o main.out main.o c.o
./main.out

OK.

Error

Without extern "C" the link fails with:

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

compile main.cpp

g++ -c -o main.o -std=c++98 main.cpp

readelf -s main.o
10: 0000000000000000    46 FUNC    GLOBAL DEFAULT    1 main
11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1fv
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __assert_fail

因为没有extern c,所以g++编译器将f看做是c++函数,使用managled name _Z1fv

compile c.c

gcc -c -o c.o -std=c89 c.c

readelf -s c.o 
8: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 f

使用c编译器,对于c函数生成unmanagled name f

link main.o and c.o

g++ -o main.out main.o c.o

main.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `f()'
collect2: error: ld returned 1 exit status

link失败,因为c++编译器需要managled name _Z1fv,然而c编译器生成了unmanagled name f

c中使用cpp方法

code

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
17
18
19
#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

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);
}

OK

compile main.c

gcc -c -o main.o -std=c89 -Wextra main.c

readelf -s main.o
10: 0000000000000000    94 FUNC    GLOBAL DEFAULT    1 main
11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND f_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __assert_fail
13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND f_float

使用c编译器,将f_int,f_float视作c函数,使用unmanagled name f_int,f_float

compile cpp.cpp

g++ -c -o cpp.o -std=c++98 cpp.cpp

readelf -s cpp.o
 9: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 _Z1fi
10: 000000000000000f    32 FUNC    GLOBAL DEFAULT    1 _Z1ff
11: 000000000000002f    23 FUNC    GLOBAL DEFAULT    1 f_int
12: 0000000000000046    31 FUNC    GLOBAL DEFAULT    1 f_float

使用c++编译器,extern c告诉c++编译器将f_int,f_float视作c函数,使用unmanagled name f_int,f_float

link main.o and cpp.o

g++ -o main.out main.o cpp.o
./main.out

OK.

Error

Without extern “C” it fails with:

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

compile main.c

gcc -c -o main.o -std=c89 -Wextra main.c

readelf -s main.o
10: 0000000000000000    94 FUNC    GLOBAL DEFAULT    1 main
11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND f_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __assert_fail
13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND f_float

使用c编译器,将f_int,f_float视作c函数,使用unmanagled name f_int,f_float

compile cpp.cpp

g++ -c -o cpp.o -std=c++98 cpp.cpp

readelf -s cpp.o
 9: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 _Z1fi
10: 000000000000000f    32 FUNC    GLOBAL DEFAULT    1 _Z1ff
11: 000000000000002f    23 FUNC    GLOBAL DEFAULT    1 _Z5f_inti
12: 0000000000000046    31 FUNC    GLOBAL DEFAULT    1 _Z5f_floatf

使用c++编译器,因为没有extern c,c++编译器将f_int,f_float视作c++函数,使用managled name _Z5f_inti,_Z5f_floatf

link main.o and cpp.o

g++ -o main.out main.o cpp.o

link失败,因为c编译器需要unmanagled name f_int,f_float,然而c++编译器生成了managled name _Z5f_inti,_Z5f_floatf

Reference

History

  • 20181226: created.