extern "C" 学习笔记

来源:互联网 发布:js indexof 判断 编辑:程序博客网 时间:2024/06/05 10:11

1.引言

有一道经典的程序员面试题,如下:


//为什么标准头文件都有和以下 相似的结构?
#ifndef DuNiangHeadFile#define DuNiangHeadFile #ifdef __cplusplusextern "C" {#endif /*bla bla bla*/ #ifdef __cplusplus}#endif #endif /* DuNiangHeadFile */

对于头文件中的编译宏的作用,显然是为了防止该头文件被重复引用。

#ifndef DuNiangHeadFile#define DuNiangHeadFile #endif /* DuNiangHeadFile */


那么剩下代码的作用又是什么呢?

#ifdef __cplusplusextern "C" {#endif /*bla bla bla*/ #ifdef __cplusplus}#endif 

这正是本篇博文所要阐述的内容。下面我们进入正题。


2.揭密extern "C"

从直观上来讲,extern "C" 显然有两层含义。其一,是 被它修饰的目标是“extern”,即该目标具有外部链接性,可以在其他编译单元(文件)中被引用。其二,被它修饰的目标是“C”类型的,即编译器或链接器要按照C的编译规则来对其进行编译或链接。

之所以采用这种方式,是因为C和C++这两种语言之间的一些差异导致的。

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

int myFunc(int a, int b);

经过C++编译器编译之后 ,在其目标文件.o 文件中,有一个_Z10myFuncii 符号,这个符号就代表了源文件中的int myFunc(int a, int b)函数了。不同的编译器可能生成不同的名字,但是都采用了相同的机制,即C++ Primer中所说的“名字粉碎”(name mangling)或“名字修饰”(name decoration)。

_Z10myFuncii 这样的名字包含了函数名、函数参数数量及类型信息,其最后的两个字符i 就表示第一参数和第二参数都是整型。C++就是靠这种机制来实现函数重载的。在链接阶段,链接器就会从生成的 .o目标文件中寻找_Z10myFuncii 这样的符号,从而生成可执行文件。

而对于加extern "C"声明后,即假设某个函数的原型为:

#ifdef __cplusplusextern "C" {#endif    int myFunc(int a, int b);#ifdef __cplusplus}#endif 

编译生成myFunc函数的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;链接器在为其他的目标代码寻找myFunc函数调用时,寻找的是未经修改的符号名_myFunc。正是因为如此,才实现了C++与C及其它语言的混合编程。


3.extern "C"的惯用法

明白了C++中extern "C"的基本原理,下面我们来具体分析一下extern "C"的常用技巧。

(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"{    #include "cExample.h"}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在C文件的头文件中直接extern "C"时会出现编译语法错误。但是,说的是直接使用会出错。如果略施小计,C语言头文件中也是可以使用extern "C"的。

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

#ifdef __cpluscplus  extern "C" {  #endif    //正常C语言头文件   #ifdef __cplusplus  }  #endif  

这是你可以修改C语言.h文件的情况。

(2)当你不能修改C语言.h文件,比如这个头文件是公司里的前辈写的,或者,公司规定禁止修改没有BUG的历史遗留代码,以防引入不必要的新Bug。

同时,.h文件中没有extern "C"关键字,而你的C++程序又要链接使用由C编译好的.o目标文件。这时该怎么办呢?

可以这样,在你的c++文件中,包含该模块的头文件时加上extern "C", 如下:

extern "C" {  #include "old_C_HeadFile.h"  }  

如果仅仅使用其中的一个函数,而不需要include整个头文件时,也可以单独声明该函数,如下:

extern "C" {  int myFunc(int , int);}  

这样,C++编译器在编译,或链接器在链接的时候,就会以C风格在目标文件中生成或寻找目标符号了。

同理,如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。这突然让我想到了com组件技术。大笑





0 0
原创粉丝点击