关于extern_C

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

#ifdef  __cplusplus
extern "C" {
#endif /**** some declaration or so *****/ #ifdef __cplusplus
}
#endif /* end of __cplusplus */

  那么,这种写法什么用呢?实际上,这是为了让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" {}这种语法形式就是用来解决这个问题的。本文将以示例对这个问题进行说明。

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

/* 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

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

 /* test_extern_c.c */

#include "test_extern_c.h"  

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

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

/* 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[]);
int b = atoi(argv[]); FOO *foo = new FOO(); foo->bar(a, b); return();
}

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

[cyc@cyc src]$ gcc -c test_extern_c.c

[cyc@cyc src]$ g++ main.cpp test_extern_c.o

[cyc@cyc src]$ ./a.out 4 5

result=9

  可以看到,程序没有任何异常,完全按照预期的方式工作。那么,如果将test_extern_c.h中的extern "C" {}所在的那几行注释掉会怎样呢?注释后的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

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

[cyc@cyc src]$ gcc -c test_extern_c.c

[cyc@cyc 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()的引用。

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

[cyc@cyc src]$ gcc -c test_extern_c.c
[cyc@cyc 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

[cyc@cyc src]$ g++ -c main.cpp
[cyc@cyc 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 _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我并不清楚,但这里并不影响我们的讨论,因此不去管它。显然,这就是原因的所在,其原理在本文开头已作了说明。

[cyc@cyc src]$ gcc -c test_extern_c.c
[cyc@cyc 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

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

[cyc@cyc src]$ g++ -c main.cpp
[cyc@cyc 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用得不是很多,了解得也很少,因此对其内部处理机制并不是很清楚,如果需要深入了解这个问题的细节请参考相关资料。

备注:

1. 对于要在cpp中使用的在c文件中写好的函数func(),只需要在c文件的头文件中添加extern "C"声明就可以了。比如:extern "C" func() { ...}

当然,可以使用

#ifdef __cplusplus

extern "C" {

#endif

#ifdef __cplusplus

}

#endif

将整个c文件的函数全都括起来

C和C++混合编译的更多相关文章

  1. makefile多目录的.c 格式.cpp混合编译

    # # c.cpp混合编译的makefile模板 # # BIN = test.exe CC = gcc CPP = g++ #这里只加入库头文件路径及库路径 INCS = -I"c:/mi ...

  2. VS2005混合编译ARM汇编代码-转

    原文地址:http://blog.csdn.net/annelcf/article/details/5468093 公司HW team有人希望可以给他们写一个在WinCE上,单独读写DDR的工具,以方 ...

  3. 混合编译.c/.cpp与.cu文件

    混合编译.c/.cpp与.cu文件 项目中用到cuda编程,写了kernel函数,需要nvcc编译器来编译..c/.cpp的文件,假定用gcc编译. 如何混合编译它们,整体思路是:.cu文件编译出的东 ...

  4. c c++ 混合编译

    单个源文件生成可执行程序 下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码: 1 2 3 4 5 6 7 8 9 /* helloworld.cpp */    # ...

  5. .Net Core Razor 预编译,动态编译,混合编译

    预编译 预编译是ASP .Net Core的默认方式.在发布时,默认会将系统中的所有Razor视图进行预编译.编译好的视图DLL统一命名为 xxx.PrecompiledViews.dll 或者 xx ...

  6. AOT和JIT以及混合编译的区别、优劣

    AOT,JIT是什么? JIT,即Just-in-time,动态(即时)编译,边运行边编译: AOT,Ahead Of Time,指运行前编译,是两种程序的编译方式 区别 这两种编译方式的主要区别在于 ...

  7. 16bit C & ASM 如何混合编译?

    起源: 今天在看以前没看完的一本书<图形程序开发人员指南>,在做里面的例子. 第一章就出问题了,一个例子“L1_2.c, L1_3.asm" ,这是C程序和ASM汇编程序的混合编 ...

  8. makefile编写---.c .cpp 混合编译makefile 模板

    # c.cpp混合编译的makefile模板 # # BIN = client_system BASE_INSTALL_DIR := /opt/arm-2009q1 BUILD_TOOL_DIR := ...

  9. 【问题】Java和Scala混合编译下无法正常使用lombok的问题

    工作中有java和scala和混合编译的工程,最近遇到一个问题,就是工程中有依赖java bean的scala文件,编译过程中发现编译器无法找到Java bean 中 lombok生成的getter, ...

  10. xmake v2.6.1 发布,使用 Lua5.4 运行时,Rust 和 C++ 混合编译支持

    xmake 是一个基于 Lua 的轻量级跨平台构建工具,使用 xmake.lua 维护项目构建,相比 makefile/CMakeLists.txt,配置语法更加简洁直观,对新手非常友好,短时间内就能 ...

随机推荐

  1. Mysql讲解数据库并发控制知识

    1.下载Mysql并安装,我喜欢不用安装的zip版,cd到bin目录下,先修改下mysql的密码. mysqladmin -u root -p password mysql ,第一次运行并修改mysq ...

  2. C#动态调用C++编写的DLL函数

    C#动态调用C++编写的DLL函数 动态加载DLL需要使用Windows API函数:LoadLibrary.GetProcAddress以及FreeLibrary.我们可以使用DllImport在C ...

  3. MongoDB项目中常用方法

    使用MongoDB连接池MongoOptions来进行连接 以及相关方法的调用 //获得驱动地址(这里的驱动 写入了配置文件中) String serverAddressStr = Configure ...

  4. POJ 2195 Going Home 最小费用流 裸题

    给出一个n*m的图,其中m是人,H是房子,.是空地,满足人的个数等于房子数. 现在让每个人都选择一个房子住,每个人只能住一间,每一间只能住一个人. 每个人可以向4个方向移动,每移动一步需要1$,问所有 ...

  5. linux命令(10)使用kill杀死含有指定关键字的进程

    命令:ps -ef|grep keyword|grep -v grep|cut -c 9-15|xargs kill -9 批量杀死包含关键字“keyword”的进程. "ps -ef&qu ...

  6. ALITUM DESIGNER 多PIN脚IC元件封装的制作

    多IC芯片的管教众多,一个一个的添加引脚效率较低,网上有好的方法,现总结如下 1 在元件库.schlib中新建元件,画出框图和添加第一个PIN脚 2利用smart paste快速放置众多PIN脚(具体 ...

  7. jquery extend中

    var $=123; <src="jquery.js"> //加载jquery.js的时候           里面有句 _$=window.$,保存123的 //no ...

  8. lable标签透明

    方法1: pictureBox1.Controls.Add(lable1);    //或 this.label1.Parent=pictureBox1;   lable1.BackColor=Col ...

  9. 查看MySQL的错误日志的方法

    我们经常在运行MySQL时会出一些错误,也经常被这些错误搞得晕头转向.当然解决这些问题的首要任务是找到日志信息. MySQL的错误信息是在data目录下的,且文件名为<hostname>. ...

  10. Singleton 单例模板

    // singleton.h #ifndef SINGLETON_H #define SINGLETON_H // 单例基类模板 template <class T> class Sing ...