前言
最近用到了动态库的函数dlsym函数,其作用是返回指定符号函数的地址。详见
深入理解Linux动态库和静态库http://blog.csdn.net/u013616945/article/details/75151418
但由于动态库文件是C++写的,如果函数不加extern “C” 符号的话,在编译的时候会提示找不到dlsym函数指定符号的函数。下面就总结extern “C”的使用。
C++/C 函数编译规则
首先需要明确的是,同一个函数在C++编译器和C编译器编译之后所生成的symbol是不一样的。如一个函数void func(int i)
,在C编译器中,生成的symbol可能单纯就是在函数名前面加个下划线,即 _func,而在C++编译器中,因C++支持函数重载,为了使不同函数能够在编译的时候区分开来,在生成symbol的时候会利用函数名粉碎机制来编译,即上面的函数可能变成了_funci&8&. 这样的话,如果在C++文件中需要调用C函数的话,就需要在此函数名加上extern “C”的关键字,让其在编译的时候使用C编译方式编译该函数。
实例说明
现在我们编写一个C程序实现两个数的加法,代码如下:
/* test_extern_c.c */
#include "test_extern_c.h"
int ThisIsTest(int a, int b)
{
return (a + b);
}
其头文件
/* file: test_extern_c.h */
#ifndef __TEST_EXTERN_C_H__
#define __TEST_EXTERN_C_H__
#ifdef __cplusplus
extern "C" {
#endif
/*
* this is a test function, which calculate
* the multiply of a and b.
*/
extern int ThisIsTest(int a, int b);
#ifdef __cplusplus
}
#endif /* end of __cplusplus */
#endif
现在我们在一个C++程序main.cpp调用这个函数
/* main.cpp */
#include "test_extern_c.h"
#include <stdio.h>
#include <stdlib.h>
class FOO {
public:
int bar(int a, int b)
{
printf("result=%i/n", ThisIsTest(a, b));
}
};
int main(int argc, char **argv)
{
int a = atoi(argv[1]);
int b = atoi(argv[2]);
FOO *foo = new FOO();
foo->bar(a, b);
return(0);
}
当我们链接这两个文件之后,得到如下结果:
[yateslaw@study TestForExtern C]$ gcc -c test_extern_c.c
[yateslaw@study TestForExtern C]$ g++ main.cpp test_extern_c.o -o main
[yateslaw@study TestForExtern C]$ ./main 4 6
result=10
可以看到,程序没有任何的异常,完全按照我们的预期工作。下一步,如果我们把test_extern_c.h文件的中extern “C” {} 注释掉,在编译链接这两个文件会得到如下结果:
[yateslaw@study TestForExtern C]$ gcc -c test_extern_c.c
[yateslaw@study TestForExtern C]$ g++ main.cpp test_extern_c.o -o main
/tmp/cc1c1QLo.o:在函数‘FOO::bar(int, int)’中:
main.cpp:(.text._ZN3FOO3barEii[_ZN3FOO3barEii]+0x1d):对‘ThisIsTest(int, int)’未定义的引用
collect2: 错误:ld 返回 1
你会发现在编译main.cpp的时候链接器会指示说找不到对ThisIsTest()函数的引用。为什么呢?其实这就是因为C++编译器与C编译器对于相同函数生成的symbol的不同导致的,更具体的方式,我们可以看看目标文件编译之后到底生成了什么symbol?
我们先编译test_extern_c.c文件,之后利用objdump反编译命令显示文件符号表信息。
[yateslaw@study TestForExtern C]$ objdump -t test_extern_c.o
test_extern_c.o: 文件格式 elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 test_extern_c.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000014 ThisIsTest
我们可以看到利用gcc编译test_extern_c.c之后,在其目标文件test_extern_c.o有一个symbol为ThisIsTest,这个symbol就是源文件定义的ThisIsTest()函数。下面我们来看看main.cpp的符号表信息。
[yateslaw@study TestForExtern C]$ g++ -c main.cpp
[yateslaw@study TestForExtern C]$ objdump -t main.o
main.o: 文件格式 elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l d .text._ZN3FOO3barEii 0000000000000000 .text._ZN3FOO3barEii
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 l d .group 0000000000000000 .group
0000000000000000 w F .text._ZN3FOO3barEii 0000000000000034 _ZN3FOO3barEii
0000000000000000 *UND* 0000000000000000 _Z10ThisIsTestii
0000000000000000 *UND* 0000000000000000 printf
0000000000000000 g F .text 0000000000000064 main
0000000000000000 *UND* 0000000000000000 atoi
0000000000000000 *UND* 0000000000000000 _Znwm
从上面我们可以看到,在利用g++编译了main.cpp之后,在其目标文件main.o有一个为_Z10ThisIsTestii的symbol,这个symbol是g++编译器利用函数名粉碎机制“粉碎”过后生成的ThisIsTest()函数的symbol。
由上面的分析我们就可以看到当我们需要在c++程序中调用C函数的时候,如果该函数名不加extern “C” 修饰的话,那么在两种语言编译器生成的该函数的symbol是不一样,这就导致了在main.cpp中找不到ThisIsTest()函数的定义了。
下面我们看看在重新加上extern “C”修饰符之后,两个文件的符号表信息。
test_extern_c.c符号表信息
//test_extern_c.c符号表信息
[yateslaw@study TestForExtern C]$ gcc -c test_extern_c.c
[yateslaw@study TestForExtern C]$ objdump -t test_extern_c.o
test_extern_c.o: 文件格式 elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 test_extern_c.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000014 ThisIsTest
main.cpp符号表信息
[yateslaw@study TestForExtern C]$ g++ -c main.cpp
[yateslaw@study TestForExtern C]$ objdump -t main.o
main.o: 文件格式 elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l d .text._ZN3FOO3barEii 0000000000000000 .text._ZN3FOO3barEii
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 l d .group 0000000000000000 .group
0000000000000000 w F .text._ZN3FOO3barEii 0000000000000034 _ZN3FOO3barEii
0000000000000000 *UND* 0000000000000000 ThisIsTest
0000000000000000 *UND* 0000000000000000 printf
0000000000000000 g F .text 0000000000000064 main
0000000000000000 *UND* 0000000000000000 atoi
0000000000000000 *UND* 0000000000000000 _Znwm
从上面两个文件的符号表信息可以看到,两个目标文件的符号表信息中,都有一个ThisIsTest的symbol,这个symbol引用的就是ThisIsTest函数。这样两个文件在链接阶段,main.cpp文件对ThisIsTest函数的使用就能找到对应的定义了。
综上,我们可以知道,使用extern “C” 这样的修饰符,可以使得CPP与C之间的接口具有互通性,不会由于不同语言的编译机制使得链接目标文件的时候出现错误。