C++中的extern "C"

比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL 输出(Export) 的函数,你需要用extern "C" 来强制编译器不要修改你的函数名。

通常,在C 语言的头文件中经常可以看到类似下面这种形式的代码:

C代码  

  1. #ifdef __cplusplus
  2. extern "C" {
  3. #endif
  4. /**** some declaration or so *****/
  5. #ifdef __cplusplus
  6. }
  7. #endif
#ifdef __cplusplus
extern "C" {
#endif

/**** some declaration or so *****/

#ifdef __cplusplus
}
#endif

那么,这种写法什么用呢?实际上,这是为了让CPP 能够与C 接口而采用的一种语法形式。之所以采用这种方式,是因为两种语言之间的一些差异所导致的。由于CPP 支持多态性,也就是具有相同函数名的函数可以完成不同的功能,CPP 通常是通过参数区分具体调用的是哪一个函数。在编译的时候,CPP 编译器会将参数类型和函数名连接在一起,于是在程序编译成为目标文件以后,CPP 编译器可以直接根据目标文件中的符号名将多个目标文件连接成一个目标文件或者可执行文件。但是在C 语言中,由于完全没有多态性的概念,C 编译器在编译时除了会在函数名前面添加一个下划线之外,什么也不会做(至少很多编译器都是这样干的)。由于这种的原因,当采用CPP 与C 混合编程的时候,就可能会出问题。假设在某一个头文件中定义了这样一个函数:

int foo(int a, int b);

而这个函数的实现位于一个.c 文件中,同时,在.cpp 文件中调用了这个函数。那么,当CPP 编译器编译这个函数的时候,就有可能会把这个函数名改成_fooii ,这里的ii 表示函数的第一参数和第二参数都是整型。而C 编译器却有可能将这个函数名编译成_foo 。也就是说,在CPP 编译器得到的目标文件中,foo() 函数是由_fooii 符号来引用的,而在C 编译器生成的目标文件中,foo() 函数是由_foo 指代的。但连接器工作的时候,它可不管上层采用的是什么语言,它只认目标文件中的符号。于是,连接器将会发现在.cpp 中调用了foo() 函数,但是在其它的目标文件中却找不到_fooii 这个符号,于是提示连接过程出错。extern "C" {} 这种语法形式就是用来解决这个问题的。本文将以示例对这个问题进行说明。

首先假设有下面这样三个文件:

C代码  

  1. /* file: test_extern_c.h */
  2. #ifndef __TEST_EXTERN_C_H__
  3. #define __TEST_EXTERN_C_H__
  4. #ifdef __cplusplus
  5. extern "C" {
  6. #endif
  7. /*
  8. * this is a test function, which calculate
  9. * the multiply of a and b.
  10. */
  11. extern int ThisIsTest(int a, int b);
  12. #ifdef __cplusplus
  13. }
  14. #endif
  15. #endif
/* 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

#endif 

在这个头文件中只定义了一个函数,ThisIsTest() 。这个函数被定义为一个外部函数,可以被包括到其它程序文件中。假设ThisIsTest() 函数的实现位于test_extern_c.c 文件中:

C代码  

  1. /* test_extern_c.c */
  2. #include "test_extern_c.h"
  3. int ThisIsTest(int a, int b)
  4. {
  5. return (a + b);
  6. }
/* test_extern_c.c */

#include "test_extern_c.h"

int ThisIsTest(int a, int b)
{
  return (a + b);
} 

可以看到,ThisIsTest() 函数的实现非常简单,就是将两个参数的相加结果返回而已。现在,假设要从CPP 中调用ThisIsTest() 函数:

Cpp代码  

  1. /* main.cpp */
  2. #include "test_extern_c.h"
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. class FOO {
  6. public:
  7. int bar(int a, int b)
  8. {
  9. printf("result=%i\n", ThisIsTest(a, b));
  10. }
  11. };
  12. int main(int argc, char **argv)
  13. {
  14. int a = atoi(argv[1]);
  15. int b = atoi(argv[2]);
  16. FOO *foo = new FOO();
  17. foo->bar(a, b);
  18. return(0);
  19. }
/* 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);
} 

在这个CPP 源文件中,定义了一个简单的类FOO ,在其成员函数bar() 中调用了ThisIsTest() 函数。下面看一下如果采用gcc 编译test_extern_c.c ,而采用g++ 编译main.cpp 并与test_extern_c.o 连接会发生什么情况:

[[email protected] src]$ gcc -c test_extern_c.c

[[email protected] src]$ g++ main.cpp test_extern_c.o

[[email protected] src]$ ./a.out 4 5

result=9

可以看到,程序没有任何异常,完全按照预期的方式工作。那么,如果将test_extern_c.h 中的extern "C" {} 所在的那几行注释掉会怎样呢?注释后的test_extern_c.h 文件内容如下:

C代码  

  1. /* test_extern_c.h */
  2. #ifndef __TEST_EXTERN_C_H__
  3. #define __TEST_EXTERN_C_H__
  4. //#ifdef   __cplusplus
  5. //extern "C" {
  6. //#endif
  7. /*
  8. /* this is a test function, which calculate
  9. * the multiply of a and b.
  10. */
  11. extern int ThisIsTest(int a, int b);
  12. //#ifdef   __cplusplus
  13. // }
  14. //#endif
  15. #endif
/* 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

#endif 

之外,其它文件不做任何的改变,仍然采用同样的方式编译test_extern_c.c 和main.cpp 文件:

[[email protected] src]$ gcc -c test_extern_c.c

[[email protected] src]$ g++ main.cpp test_extern_c.o

/tmp/cca4EtJJ.o(.gnu.linkonce.t._ZN3FOO3barEii+0x10): In function `FOO::bar(int, int)‘:

: undefined reference to `ThisIsTest(int, int)‘

collect2: ld returned 1 exit status

在编译main.cpp 的时候就会出错,连接器ld 提示找不到对函数ThisIsTest() 的引用。

为了更清楚地说明问题的原因,我们采用下面的方式先把目标文件编译出来,然后看目标文件中到底都有些什么符号:

[[email protected] src]$ gcc -c test_extern_c.c

[[email protected] src]$ objdump -t test_extern_c.o

test_extern_c.o:   file format elf32-i386

SYMBOL TABLE:

00000000 l   df *ABS* 00000000 test_extern_c.c

00000000 l   d .text 00000000

00000000 l   d .data 00000000

00000000 l   d .bss   00000000

00000000 l   d .comment     00000000

00000000 g   F .text 0000000b ThisIsTest

[[email protected] src]$ g++ -c main.cpp

[[email protected] src]$ objdump -t main.o

main.o:   file format elf32-i386

MYMBOL TABLE:

00000000 l   df *ABS* 00000000 main.cpp

00000000 l   d .text 00000000

00000000 l   d .data 00000000

00000000 l   d .bss   00000000

00000000 l   d .rodata     00000000

00000000 l   d .gnu.linkonce.t._ZN3FOO3barEii 00000000

00000000 l   d .eh_frame     00000000

00000000 l   d .comment     00000000

00000000 g   F .text 00000081 main

00000000       *UND* 00000000 atoi

00000000       *UND* 00000000 _Znwj

00000000       *UND* 00000000 _ZdlPv

00000000 w   F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii

00000000       *UND* 00000000 _Z10ThisIsTestii

00000000       *UND* 00000000 printf

00000000       *UND* 00000000 __gxx_personality_v0

可以看到,采用gcc 编译了test_extern_c.c 之后,在其目标文件test_extern_c.o 中的有一个ThisIsTest 符号,这个符号就是源文件中定义的ThisIsTest() 函数了。而在采用g++ 编译了main.cpp 之后,在其目标文件main.o 中有一个_Z10ThisIsTestii 符号,这个就是经过g++ 编译器“粉碎”过后的函数名。其最后的两个字符i 就表示第一参数和第二参数都是整型。而为什么要加一个前缀_Z10 我并不清楚,但这里并不影响我们的讨论,因此不去管它。显然,这就是原因的所在,其原理在本文开头已作了说明。

那么,为什么采用了extern "C" {} 形式就不会有这个问题呢,我们就来看一下当test_extern_c.h 采用extern "C" {} 的形式时编译出来的目标文件中又有哪些符号:

[[email protected] src]$ gcc -c test_extern_c.c

[[email protected] src]$ objdump -t test_extern_c.o

test_extern_c.o:   file format elf32-i386

SYMBOL TABLE:

00000000 l   df *ABS* 00000000 test_extern_c.c

00000000 l   d .text 00000000

00000000 l   d .data 00000000

00000000 l   d .bss   00000000

00000000 l   d .comment     00000000

00000000 g   F .text 0000000b ThisIsTest

[[email protected] src]$ g++ -c main.cpp

[[email protected] src]$ objdump -t main.o

main.o:   file format elf32-i386

SYMBOL TABLE:

00000000 l   df *ABS* 00000000 main.cpp

00000000 l   d .text 00000000

00000000 l   d .data 00000000

00000000 l   d .bss   00000000

00000000 l   d .rodata     00000000

00000000 l   d .gnu.linkonce.t._ZN3FOO3barEii 00000000

00000000 l   d .eh_frame     00000000

00000000 l   d .comment     00000000

00000000 g   F .text 00000081 main

00000000       *UND* 00000000 atoi

00000000       *UND* 00000000 _Znwj

00000000       *UND* 00000000 _ZdlPv

00000000 w   F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii

00000000       *UND* 00000000 ThisIsTest

00000000       *UND* 00000000 printf

00000000       *UND* 00000000 __gxx_personality_v0

注意到这里和前面有什么不同没有,可以看到,在两个目标文件中,都有一个符号ThisIsTest ,这个符号引用的就是ThisIsTest() 函数了。显然,此时在两个目标文件中都存在同样的ThisIsTest 符号,因此认为它们引用的实际上同一个函数,于是就将两个目标文件连接在一起,凡是出现程序代码段中有ThisIsTest 符号的地方都用ThisIsTest() 函数的实际地址代替。另外,还可以看到,仅仅被extern "C" {} 包围起来的函数采用这样的目标符号形式,对于main.cpp 中的FOO 类的成员函数,在两种编译方式后的符号名都是经过“粉碎”了的。

因此,综合上面的分析,我们可以得出如下结论:采用extern "C" {} 这种形式的声明,可以使得CPP 与C 之间的接口具有互通性,不会由于语言内部的机制导致连接目标文件的时候出现错误。需要说明的是,上面只是根据我的试验结果而得出的结论。由于对于CPP 用得不是很多,了解得也很少,因此对其内部处理机制并不是很清楚,如果需要深入了解这个问题的细节请参考相关资料。

注意:

用g++编译cpp程序时,编译器会定义宏 __cplusplus ,可根据__cplusplus是否定义决定是否需要extern "C"。

总结:

上面讲的都是理论,和一些程序,那么实际使用时有以下集中情况:

1. 现在要写一个c语言的模块,供以后使用(以后的项目可能是c的也可能是c++的),源文件事先编译好,编译成.so或.o都无所谓。头文件中声明函数时要用条件编译包含起来,如下:

C代码  

  1. #ifdef __cpluscplus
  2. extern "C" {
  3. #endif
  4. //some code
  5. #ifdef __cplusplus
  6. }
  7. #endif
#ifdef __cpluscplus
extern "C" {
#endif

//some code

#ifdef __cplusplus
}
#endif

也就是把所有函数声明放在some code的位置。

2. 如果这个模块已经存在了,可能是公司里的前辈写的,反正就是已经存在了,模块的.h文件中没有extern "C"关键字,这个模块又不希望被改动的情况下,可以这样,在你的c++文件中,包含该模块的头文件时加上extern "C", 如下:

Cpp代码  

  1. extern "C" {
  2. #include "test_extern_c.h"
  3. }
extern "C" {
#include "test_extern_c.h"
}

3.上面例子中,如果仅仅使用模块中的1个函数,而不需要include整个模块时,可以不include头文件,而单独声明该函数,像这样:

Cpp代码  

  1. extern "C" {
  2. int ThisIsTest(int, int);
  3. }
extern "C" {
int ThisIsTest(int, int);
}

然后就可一使用模块中的这个ThisIsTest函数了。

时间: 2024-10-27 00:49:03

C++中的extern "C"的相关文章

【转载】c++中的 extern &quot;C&quot;(讲的更好一些)

[说明]本文章转载自 东边日出西边雨 的文章http://songpengfei.iteye.com/blog/1100239 ----------------------------------------- 比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL 输出(Export) 的函数,你需要用extern "C" 来强制编译器不要修改你的函数名. 通常,在C 语言的头文件中经常可以看到类似下面这种形式的代码: C代码   #ifdef __cplus

DSP 中关键字extern,cregister,Near ,Far,restrict,volatile

extern:extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.另外,extern也可用来进行链接指定. const: 可以用const定义一些变量或数组,以确保它的值不被改变. 例:int *const p = &x;  定义一个常量指针p给变量int.       const int *q = &x;  定义一个变量指针q给常量int. cregister:定义用来直接访问CPU控制寄存器的变量. 你可以先定义

C语言中的 extern 关键字

今天在 BLE 中看到很多 extern 关键字,现在总结一下: extern 关键字主要用于在一个c文件中要用到另一个c文件中的变量或者函数. example: 1 #extern_base.c 2 3 int a = 100; 1 #extern.h 2 3 extern int a; 1 #extern.c 2 3 #include <stdio.h> 4 #include "extern.h" 5 6 int main() 7 { 8 printf("%d

关于c语言中关键字extern用法。

看c语言的ppt看到extern中的生命外部变量,懵逼了.然后在网上查了关于extern的用法.都不是我想要的. 找了半个小时,又写写程序,算是明白了. extern声明外部变量时候,也就是在别的函数里面,一个文件已经有了外部变量,int A=13,B=0:当我别的函数想用这个A,B变量的值得时候, 可以在函数里面用extern声明即:extern A,B:我觉得也就是扩展的意思,有点类似java中继承的味道(楼主java半路转了啊,尴尬.). 测试代码如下: #include<stdio.h>

c与c++中的extern const的区别和联系

最近复习c++,发现了这个东西. c语言里面,我们在一个.c文件中用const定义了一个全局变量后,可以在另一个.c文件中用extern const来引用,但在c++中在链接的时候会报undefined reference错误,这是因为在c语言里面,const全局变量在符号表里面是GLOBAL即全局可见的,而在c++里面却是LOCAL即只能在当前编译模块可见. e.g. 在a.h中: 1 extern const int a; 在a.c中: 1 const int a = 10; 在main.c

C++项目中的extern &quot;C&quot; {}

引言 在用C++的项目源码中,经常会不可避免的会看到下面的代码: 1 2 3 4 5 6 7 8 9 #ifdef __cplusplus extern "C" { #endif /*...*/ #ifdef __cplusplus } #endif 它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试or笔试中.下面我就从以下几个方面来介绍它: 1.#ifdef _cplusplus/#endif _cplusplus及发散 2.extern "C" 2.1

D语言中使用extern(Windows)类重载Object方法时的问题

使用D语言已经有快20天了,已经完成项目需要的基本功能,但还需进一步完善. 最近两三天在DGUI基础上移植了几个Window窗口控件,有TreeList(树+列表视图),DataList(数据列表),MdiFrom(多窗口),IEWebBrowser(IE浏览器),SciEditor(Sci源代码编辑器),DockPanel(notpad的Docking插件),SysHeader(表头控件), SysLink等几个标准Windows控件. 感谢DGUI的作者,对Windows控件做的布局管理及封

[转] C++项目中的extern &quot;C&quot; {}

点击阅读原文 引言 在用C++的项目源码中,经常会不可避免的会看到下面的代码: #ifdef __cplusplus extern "C" { #endif /*...*/ #ifdef __cplusplus } #endif 它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试or笔试中.下面我就从以下几个方面来介绍它: 1.#ifdef _cplusplus/#endif _cplusplus及发散 2.extern "C" 2.1.extern关键字

宏定义中使用extern

全局变量的定义是会得到内存分配且可以被其他模块通过C语言中extern关键字调用的变量.所以,必须在 .C 或 .H 文件中定义,此种方法很容易导致错误. 以下为UCOSIII中定义全局变量的方法: 部分源码(Os.h文件中): #ifdef OS_GLOBALS #define OS_EXT #else #define OS_EXT extern #endif Os.h文件中定义了全局变量OS_EXT:在文件"Os_var.c"文件中定义了全局变量OS_GLOBALS 源码如下(Os