C和C++混合编译

关于extern_C

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

[plain] view plaincopyprint?

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

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

[plain] view plaincopyprint?

  1. int foo(int a, int b);

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

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

[plain] view plaincopyprint?

  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  /* end of __cplusplus */
  15. #endif

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

[plain] view plaincopyprint?

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

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

[plain] view plaincopyprint?

  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. }

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

[plain] view plaincopyprint?

  1. [[email protected] src]$ gcc -c test_extern_c.c
  2. [[email protected] src]$ g++ main.cpp test_extern_c.o
  3. [[email protected] src]$ ./a.out 4 5
  4. result=9

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

[plain] view plaincopyprint?

  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   /* end of __cplusplus */
  15. #endif

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

[plain] view plaincopyprint?

  1. [[email protected] src]$ gcc -c test_extern_c.c
  2. [[email protected] src]$ g++ main.cpp test_extern_c.o
  3. /tmp/cca4EtJJ.o(.gnu.linkonce.t._ZN3FOO3barEii+0x10): In function `FOO::bar(int, int)‘:
  4. : undefined reference to `ThisIsTest(int, int)‘
  5. collect2: ld returned 1 exit status

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

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

[plain] view plaincopyprint?

  1. [[email protected] src]$ gcc -c test_extern_c.c
  2. [[email protected] src]$ objdump -t test_extern_c.o
  3. test_extern_c.o:     file format elf32-i386
  4. SYMBOL TABLE:
  5. 00000000 l    df *ABS*  00000000 test_extern_c.c
  6. 00000000 l    d  .text  00000000
  7. 00000000 l    d  .data  00000000
  8. 00000000 l    d  .bss   00000000
  9. 00000000 l    d  .comment       00000000
  10. 00000000 g     F .text  0000000b ThisIsTest
  11. [[email protected] src]$ g++ -c main.cpp
  12. [[email protected] src]$ objdump -t main.o
  13. main.o:     file format elf32-i386
  14. SYMBOL TABLE:
  15. 00000000 l    df *ABS*  00000000 main.cpp
  16. 00000000 l    d  .text  00000000
  17. 00000000 l    d  .data  00000000
  18. 00000000 l    d  .bss   00000000
  19. 00000000 l    d  .rodata        00000000
  20. 00000000 l    d  .gnu.linkonce.t._ZN3FOO3barEii 00000000
  21. 00000000 l    d  .eh_frame      00000000
  22. 00000000 l    d  .comment       00000000
  23. 00000000 g     F .text  00000081 main
  24. 00000000         *UND*  00000000 atoi
  25. 00000000         *UND*  00000000 _Znwj
  26. 00000000         *UND*  00000000 _ZdlPv
  27. 00000000  w    F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii
  28. 00000000         *UND*  00000000 _Z10ThisIsTestii
  29. 00000000         *UND*  00000000 printf
  30. 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我并不清楚,但这里并不影响我们的讨论,因此不去管它。显然,这就是原因的所在,其原理在本文开头已作了说明。

[plain] view plaincopyprint?

  1. [[email protected] src]$ gcc -c test_extern_c.c
  2. [[email protected] src]$ objdump -t test_extern_c.o
  3. test_extern_c.o:     file format elf32-i386
  4. SYMBOL TABLE:
  5. 00000000 l    df *ABS*  00000000 test_extern_c.c
  6. 00000000 l    d  .text  00000000
  7. 00000000 l    d  .data  00000000
  8. 00000000 l    d  .bss   00000000
  9. 00000000 l    d  .comment       00000000
  10. 00000000 g     F .text  0000000b ThisIsTest

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

[plain] view plaincopyprint?

  1. [[email protected] src]$ g++ -c main.cpp
  2. [[email protected] src]$ objdump -t main.o
  3. main.o:     file format elf32-i386
  4. SYMBOL TABLE:
  5. 00000000 l    df *ABS*  00000000 main.cpp
  6. 00000000 l    d  .text  00000000
  7. 00000000 l    d  .data  00000000
  8. 00000000 l    d  .bss   00000000
  9. 00000000 l    d  .rodata        00000000
  10. 00000000 l    d  .gnu.linkonce.t._ZN3FOO3barEii 00000000
  11. 00000000 l    d  .eh_frame      00000000
  12. 00000000 l    d  .comment       00000000
  13. 00000000 g     F .text  00000081 main
  14. 00000000         *UND*  00000000 atoi
  15. 00000000         *UND*  00000000 _Znwj
  16. 00000000         *UND*  00000000 _ZdlPv
  17. 00000000  w    F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii
  18. 00000000         *UND*  00000000 ThisIsTest
  19. 00000000         *UND*  00000000 printf
  20. 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文件的函数全都括起来。

时间: 2024-12-23 12:28:14

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

ASP.NET(C#)与MATLAB混合编译

最近在实现ASP.NET与MATLAB混合编译,几乎看遍了网络上所有同学们的解决方法,总体来说有两种实现方法.一种是利用MLApp.MLAppClass直接执行MATLAB.另一种是利用MATLAB自带的编译器MCR将.M文件编译为DLL文件,在ASP.NET中引用并且使用. 具体方法请参考MATLAB官网给出的一个例子,非常详尽的说明了第二种方法的正确使用方法.具体步骤就不细写了,只把最关键的代码留下,供大家学习. http://cn.mathworks.com/support/2013a/d

在Xcode下OC和C++混合编译出现的问题总结

简单的说下最近一次遇到了混编中的问题,算是自己的总结吧,还望能够帮助到打家. 当项目中OC和C++要混合编译的时候,特别是在两种语言字一个文件中相互调用的时候,千万一定不要忘记把编译器的Compile Sources As选项改为Objective C++,因为默认的选择项是According to file type.不然就会报错,而且报的错怪的很,当时搞的头都大了.

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

# c.cpp混合编译的makefile模板 # # BIN = client_system BASE_INSTALL_DIR := /opt/arm-2009q1 BUILD_TOOL_DIR := $(BASE_INSTALL_DIR) BUILD_TOOL_PREFIX := $(BUILD_TOOL_DIR)/bin/arm-none-linux-gnueabi- CC = $(BUILD_TOOL_PREFIX)gccCPP = $(BUILD_TOOL_PREFIX)g++ INCS

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

原文地址:http://blog.csdn.net/annelcf/article/details/5468093 公司HW team有人希望可以给他们写一个在WinCE上,单独读写DDR的工具,以方便他们量测Memory读写时的硬件信号. 在开发过程中,发现简单的在Storage Memory区域拷贝或粘贴文件不能达到硬件量测的要求,需要直接通过编写ARM汇编指令让CPU直接对Memory进行读写数据. 以 前没有用VS2005编写过汇编代码,所以走了点弯路,一直试图用内嵌汇编的方式来buil

iPhone开发中,在XCode下混合编译 C++/Objective-C

首先,最最最要紧的事情,不是代码而是编译器选项,在做混合编译之前一定要把编译器的Compile Sources As选项改为Objective C++. 默认的选项是According to file type,用这个的话,你后面每个不在交叉行列里的类都OK,一旦两种语言在一个文件中相互调用, 就会报错,而且报的错怪的很,比如:找不到new,找不到delete,等等.

16bit C &amp; ASM 如何混合编译?

起源: 今天在看以前没看完的一本书<图形程序开发人员指南>,在做里面的例子. 第一章就出问题了,一个例子“L1_2.c, L1_3.asm" ,这是C程序和ASM汇编程序的混合编译问题. 总是报各种错误,无法实现. 原因: 当时是2000年左右,应该是DOS环境,16bit的程序. 一开始,忽略了这么多平台限制,先用gcc编译,后来用nasm编译那段asm程序,要改个小语法,还报几个错. 解决: 后来还是用古老的 TC 2.0 作 C程序的编译器,以及链接器. 那个 asm汇编文件

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

预编译 预编译是ASP .Net Core的默认方式.在发布时,默认会将系统中的所有Razor视图进行预编译.编译好的视图DLL统一命名为 xxx.PrecompiledViews.dll 或者 xxx.Views.dll 动态编译 将项目整个配置成动态编译很简单,添加一个配置项目MvcRazorCompileOnPublish,值为false即可 <PropertyGroup> <MvcRazorCompileOnPublish>false</MvcRazorCompile

C++下混合编译c语言方法总结

最近在读SGI STL源码,感觉对C++的学习很有帮助,之前对于泛型.iterator.traits等等各种特性的概念非常模糊,通过这两天的琢磨,再加上<STL 源码剖析>的帮助,对C++那诡异的语法也不再害怕了. 在其中遇到的一些问题,总结如下: 1. C++空白基类最优化(EBO) 参考:http://www.xuebuyuan.com/1610977.html 如果有一个空类,如下.其所占空间并非0byte,而通常是1 byte. class A{ }; 如果有一个类,如下.而a 占1

swift 和 OC 混合编译的问题

最近在学习swift,很想在之前OC的项目中使用swift 开始的时候,上手还算比较快,下面记录下简单的一些坑. 1.由于接触的swfit一些教程,所以准备在已有的OC里面使用swift,按照教程,没问题,但是我在原来的OC中使用swift,怎么也编译不通过, 开始的时候报错,提示找不到各种类,后来仔细发现,原来不可以在同一个项目中即在oc代码中使用swfit代码,并且在swift代码中使用OC,类似于死循环的感觉 需要先编译出项目名-swift.h才可以. 2.原来的OC在转化方法的时候,主要