C和C++互相调用 以及 extern "C"的用法

来源:互联网 发布:网络角色去衣图 编辑:程序博客网 时间:2024/04/28 07:57

转载地址:http://blog.sina.com.cn/s/blog_6dd65c6f01010lon.html
首先,先看一下下面这个例子:
这里写图片描述
它到底有什么用呢,这样的问题会出现在面试or笔试中,我觉得作为一个学C++的童鞋,应该了解一下。下面我就从以下几个方面来介绍它:
1、#ifdef _cplusplus/#endif _cplusplus
2、extern “C”
2.1、extern关键字
2.2、”C”
2.3、小结extern “C”
3、C和C++互相调用
3.1、C++的编译和连接
3.2、C的编译和连接
3.3、C++中调用C的代码
3.4、C中调用C++的代码
4、C和C++混合调用特别之处函数指针

1、#ifdef _cplusplus/#endif _cplusplus
在介绍extern “C”之前,我们来看下#ifdef _cplusplus/#endif _cplusplus的作用。很明显#ifdef/#endif、#ifndef/#endif用于条件编译,#ifdef _cplusplus/#endif _cplusplus表示如果定义了宏_cplusplus,就执行#ifdef/#endif之间的语句,否则就不执行。
在这里为什么需要#ifdef _cplusplus/#endif _cplusplus呢?这是C++的条件编译,是四种预编译命令之一(头文件包含,条件编译,宏替换和布局控制)!
既然说到了条件编译,我就介绍它的一个重要应用——避免重复包含头文件。为了防止头文件重复包含,在C++中,经常会使用到条件编译。
现在假设有如下场景:现有A, B, C三个头文件,它们之间的相互关系如下:文件B,C中会直接包含文件A,而文件C中同时会包含文件B,这样通过直接及间接包含,文件C包含了文件A两次, 通常编译的时候就会提醒变量重复定义等问题,这就是有重复包含导致的。C++中通过条件编译解决了这个问题,在文件C中第一次包含文件A时,不会有问题, 而当第二次包含时,条件编译起了作用,组织了重复包含,从而确保了程序的正确性。
下面是我近期写的一个C++头文件,该头文件会被多个其他头文件包含,这里使用条件编译解决问题。
这里写图片描述
2、extern “C”
首先从字面上分析extern “C”,它由两部分组成——extern关键字、”C”。下面我就从这两个方面来解读extern “C”的含义。
2.1、extern关键字
在C++中用extern关键字来声明变量和函数;对于函数而言,由于函数的声明如“extern int method();”与函数定义“int method(){}”可以很清晰的区分开来,为了简便起见,可以把extern关键字省略,于是有了我们常见的函数声明方式“int method();”,然而对于变量并非如此,变量的定义格式如“int i;”,声明格式为“extern int i;”,如果省略extern关键字,就会造成混乱,故不允许省略。

这里写图片描述
图3 左为file1.h,右为file2.cpp

经本人测试,上面的程序可以省略掉“extern int x”和“int f()”,而只包含”#include “file1.h”“。但是反过来不行,也就是:不可以省略掉”#include “file1.h”“,而仅仅只包含“extern int x”和“int f()”,这样file2.cpp会找不到变量x和函数f()。
回到extern关键字,extern是C/C++语言中表明函数和全局变量作用 范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函 数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块 A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与extern对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
2.2、”C”
典型的,一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。不同语言编写的代码互相调用是困难 的,甚至是同一种编写的代码但不同的编译器编译的代码。例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一 样。
为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。例如,声明C和C++标准库函数strcyp(),并指定它应该根据C的编译和连接规约来链接:

extern “C” char* strcpy(char*,const char*);
注意它与下面的声明的不同之处:
extern char* strcpy(char*,const char*);
下面的这个声明仅表示在连接的时候调用strcpy()。
extern “C”指令非常有用,因为C和C++的近亲关系。注意:extern “C”指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。
还有要说明的是,extern “C”指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern “C”,仍然要遵守C++的类型检测、参数转换规则。
再看下面的一个例子,为了声明一个变量而不是定义一个变量,你必须在声明时指定extern关键字,但是当你又加上了”C”,它不会改变语义,但是会改变它的编译和连接方式。如果你有很多语言要加上extern “C”,你可以将它们放到extern “C”{ }中。
2.3、小结extern “C”
通过上面两节的分析,我们知道extern “C”的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)
3、C和C++互相调用
我们既然知道extern “C”是实现的类C和C++的混合编程。下面我们就分别介绍如何在C++中调用C的代码、C中调用C++的代码。首先要明白C和C++互相调用,你得知道它们之间的编译和连接差异,及如何利用extern “C”来实现相互调用。
3.1、C++的编译和连接
C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:
1 void print(int i);
2 void print(char c);
3 void print(float f);
4 void print(char* s);
编译为:
1 _print_int
2 _print_char
3 _print_float
4 _pirnt_string
这样的函数名,来唯一标识每个函数,这就是C++中的名字修饰,也有很多优势,可以确保类型安全连接。注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找 _print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。
C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。
3.2、C的编译和连接
C语言中并没有重载和类这些特性,故并不像C++那样print(int i),会被编译为_print_int,而是直接编译为_print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的 print(3)时,它会去找_print_int(3)。因此extern “C”的作用就体现出来了。
3.3、C++中调用C的代码
假设一个C的头文件cHeader.h中包含一个函数print(int i),为了在C++中能够调用它,必须要加上extern关键字(原因在extern关键字那节已经介绍)。它的代码如下:
1 #ifndef C_HEADER
2 #define C_HEADER
3
4 extern void print(int i);
5
6 #endif
相对应的实现文件为cHeader.c的代码为:
1 #include

1 0