您的位置:首页 > 编程语言 > C语言/C++

使用#ifdef _cplusplus 实现c 和 c++的混合编程

2015-07-11 08:03 507 查看
最近在网上下了一个SVM Light的源码包,它使用c语言写的。因为一直以来使用的都是C++,所以希望能用C++写自己的函数然后调用SVM Light的接口函数实现混合编程。

在查看SVM Light的源码的时候经常会发现

#ifdef _cplusplus

extern "C"{

#endif

#include [...头文件]

[函数声明]

[变量声明]

...

#ifdef _cplusplus

}

#endif

这个有什么作用呢?蓝色部分转自:http://www.2cto.com/kf/201302/191822.html

在C++中使用C程序

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:voidfoo( int x, int y );

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。

比如下面的一段简单的函数,我们看看加入和不加入extern "C"产生的汇编代码都有哪些变化:

int f(void)

{

return 1;

}

  在加入extern "C"的时候产生的汇编代码是:

.file "test.cxx"

.text

.align 2

.globl _f

.def _f; .scl 2; .type 32; .endef

_f:

pushl %ebp

movl %esp, %ebp

movl $1, %eax

popl %ebp

ret

  但是不加入了extern "C"之后

.file "test.cxx"

.text

.align 2

.globl __Z1fv

.def __Z1fv; .scl 2; .type 32; .endef

__Z1fv:

pushl %ebp

movl %esp, %ebp

movl $1, %eax

popl %ebp

ret

两段汇编代码同样都是使用gcc -S命令产生的,所有的地方都是一样的,唯独是产生的函数名,一个是_f,一个是__Z1fv。

  明白了加入与不加入extern "C"之后对函数名称产生的影响,我们继续我们的讨论:为什么需要使用extern "C"呢?C++之父在设计C++之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。

  试想这样的情况:一个库文件已经用C写好了而且运行得很良好,这个时候我们需要使用这个库文件,但是我们需要使用C++来写这个新的代码。如果这个代码使用的是C++的方式链接这个C库文件的话,那么就会出现链接错误.我们来看一段代码:首先,我们使用C的处理方式来写一个函数,也就是说假设这个函数当时是用C写成的:

//f1.c

extern "C"

{

void f1()

{

return;

}

}

  编译命令是:gcc -c f1.c -o f1.o 产生了一个叫f1.o的库文件。再写一段代码调用这个f1函数:

// test.cxx

//这个extern表示f1函数在别的地方定义,这样可以通过

//编译,但是链接的时候还是需要

//链接上原来的库文件.

extern void f1();

int main()

{

f1();

return 0;

}

  通过gcc -c test.cxx -o test.o 产生一个叫test.o的文件。然后,我们使用gcc test.o f1.o来链接两个文件,可是出错了,错误的提示是:

test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'

  也就是说,在编译test.cxx的时候编译器是使用C++的方式来处理f1()函数的,但是实际上链接的库文件却是用C的方式来处理函数的,所以就会出现链接过不去的错误:因为链接器找不到函数。

  因此,为了在C++代码中调用用C写成的库文件,就需要用extern "C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。

  比如,现在我们有了一个C库文件,它的头文件是f.h,产生的lib文件是f.lib,那么我们如果要在C++中使用这个库文件,我们需要这样写:

extern "C"

{

#include "f.h"

}

  回到上面的问题,如果要改正链接错误,我们需要这样子改写test.cxx:

extern "C"

{

extern void f1();

}

int main()

{

f1();

return 0;

}

  重新编译并且链接就可以过去了.

总结起来就是:

如果我们编写的是一个C++程序,C++编译器将自动定义_cplusplus宏,从而#ifdef _cplusplus起作用,预编译之后上面的宏定义变为:

extern "C"{

#include [...头文件]

[函数声明]

[变量声明]

...

}

被包含在extern "C" 里面的函数声明其实是告诉编译器当遇到对应的函数调用的时候将待解析符号编译为C的形式,而包含在extern "C"里面的函数定义是告诉编译器在将函数定义放在已解析符号的时候应该编译为C的形式。这一点是我开始的时候没想明白的。

也就是说两个宏定义之间的内容被声明为是用C语言编写和编译的,C++编译器在遇到其对应的函数或者变量时会将其编译成C的形式,使得链接的时候能找到对应的C函数定义。

如果我们编写的是一个C程序,则_cplusplus宏无效,预编译之后上述内容等价于:

#include [...头文件]

[函数声明]

[变量声明]

...

上述内容都将以C的形式被编译


在C中使用C++程序


有时候需要在C程序中使用C++语言写好的源程序(注意是源程序,如果是编译好的obj那么C程序是没办法调用的),这时候也能使用external “C" 对C++程序进行声明从而用C的形式对C++程序进行编译。

因为先有C后有C++, 所以只能从C++的代码中考虑了.

加入C++中的函数或变量有可能被C中的文件调用,则应该这样写,也是用extern "C"{}

不过是代码中要加,头文件也要加,因为可能是C++中也调用:代码中加是将C++函数对应的解析符号编译成C的形式;头文件中加是告诉调用该函数的C++程序,该C++函数已被编译成了C的形式,所以要链接的的话只能以C的形式。

--------------------------------------

cpp.h的实现


#ifndef _c_h_

#define _c_h_

#ifdef __cplusplus

extern "C" {

#endif

void CPP_fun();

#ifdef __cplusplus

}

#endif

#endif

.-------------------------------------------

Cpp.cpp的实现

extern "C" { //告诉C+++编译器,扩号里按照C的命名规则编译

void CPP_fun()

{

.....

}

以上代码来自:http://blog.163.com/hanxiaoyang88@126/blog/static/133346643201089365047/


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: