C C++混编

本文主要针对工程代码中C和C++混编需要注意的一个点进行记录。

示例代码如下所示:

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

上一篇:Python之configparser模块详解和使用


下一篇:3.2线程同步