本文主要针对工程代码中C和C++混编需要注意的一个点进行记录。
示例代码如下所示:
编译结果如下所示:
[... demo]$ ./build.sh -- Configuring done -- Generating done -- Build files have been written to: /home/10260390@zte.intra/桌面/demo/build [ 33%] Building C object CMakeFiles/hello_world.dir/main.c.o [ 66%] Building C object CMakeFiles/hello_world.dir/print.c.o [100%] Linking C executable hello_world [100%] Built target hello_world
对于以上纯C语言代码的工程来说并没有什么问题。
接下来我们把 main.c 文件的名字改为 main.cc,文件内容保持不变,重新编译。
[... demo]$ mv main.c main.cc [... demo]$ ./build.sh -- Configuring done -- Generating done -- Build files have been written to: /home/10260390@zte.intra/桌面/demo/build Scanning dependencies of target hello_world [ 33%] Building CXX object CMakeFiles/hello_world.dir/main.cc.o [ 66%] Building C object CMakeFiles/hello_world.dir/print.c.o [100%] Linking CXX executable hello_world CMakeFiles/hello_world.dir/main.cc.o:在函数‘main’中: main.cc:(.text+0x5):对‘print()’未定义的引用 collect2: 错误:ld 返回 1 CMakeFiles/hello_world.dir/build.make:117: recipe for target 'hello_world' failed make[2]: *** [hello_world] Error 1 CMakeFiles/Makefile2:94: recipe for target 'CMakeFiles/hello_world.dir/all' failed make[1]: *** [CMakeFiles/hello_world.dir/all] Error 2 Makefile:102: recipe for target 'all' failed make: *** [all] Error 2
发现会报 main.cc:(.text+0x5):对‘print()’未定义的引用 的错误。究其原因,是C语言和C++符号修饰机制不同导致的这个问题。
下面我们通过分解工程编译的步骤来深入剖析这个问题的本质。
[... demo]$ gcc -c print.c -o print.o [... demo]$ g++ -c main.cc -o main.o [... demo]$ readelf -s print.o Symbol table '.symtab' contains 11 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS print.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 8 8: 0000000000000000 0 SECTION LOCAL DEFAULT 6 9: 0000000000000000 17 FUNC GLOBAL DEFAULT 1 print 10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts [... demo]$ readelf -s main.o Symbol table '.symtab' contains 10 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.cc 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 6 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 8: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 main 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z5printv
可见在 print.c 编译得到的重定位目标文件 print.o中 print 会被修饰为 print, 而 main.cc 编译得到的重定位目标文件 main.o 中 print 会被修饰为 _Z5printv。由于链接时会对外部函数的引用进行重定位,因此需要在全局符号表中寻找_Z5printv,找不到就会报未定义的错误。
对于这种问题,有一种通用的解决方式,即在头文件 print.h 中使用 extern ”C“。但有个问题是。C语言并不支持 extern ”C“ 语法,如果为了兼容 C 语言和 C++ 而定义两个头文件又太麻烦。幸好我们有一种很好的方法解决上述问题,那就是使用 C++ 的宏 __cplusplus,C++ 编译器会在编译 C++ 程序时默认定义这个宏,程序中就可以用这个条件宏来判断当前编译单元是否为 C++ 源文件。具体代码如下:
#ifndef _PRINT_H #define _PRINT_H #ifdef __cplusplus extern "C" { #endif void print(); #ifdef __cplusplus } #endif #endif
重新编译,无论 print.h 被 C 语言源文件还是被 C++ 源文件包含,print 都会按照 C 语言的方式进行符号修饰,自然就不会再报符号未定义的错误了。
[... demo]$ g++ -c main.cc -o main.o [... demo]$ readelf -s main.o Symbol table '.symtab' contains 10 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.cc 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 6 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 8: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 main 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND print