深入理解extern "C" 的使用

前言

最近用到了动态库的函数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之间的接口具有互通性,不会由于不同语言的编译机制使得链接目标文件的时候出现错误。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值