本文主要针对工程代码中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