您的位置:首页 > 其它

链接指示符extern “C”

2016-06-23 21:50 176 查看

extern "c"用法解析

引言

C++保留了一部分过程式语言的特点,因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL输出(Export)的函数,你需要用extern "C"来强制编译器不要修改你的函数名。

揭秘extern "C"

从标准头文件说起

#ifndef __INCvxWorksh  /*防止该头文件被重复引用*/
#define __INCvxWorksh

#ifdef __cplusplus    //__cplusplus是cpp中自定义的一个宏
extern "C" {          //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#endif

/**** some declaration or so *****/

#ifdef __cplusplus
}
#endif

#endif /* __INCvxWorksh */

extern "C"的含义

extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。

被extern "C"限定的函数或变量是extern类型的;

1、extern关键字

extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在链接阶段中从模块A编译生成的目标代码中找到此函数。

与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

2、被extern "C"修饰的变量和函数是按照C语言方式编译和链接的

首先看看C++中对类似C的函数是怎样编译的。

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

void foo( 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++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

3、举例说明

(1)未加extern "C"声明时的连接方式

假设在C++中,模块A的头文件如下:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif

//在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);

实际上,在连接阶段,链接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

(2)加extern "C"声明后的编译和链接方式

加extern "C"声明后,模块A的头文件变为:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

<1>A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

<2>链接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo(int x, int y),则模块B找不到模块A中的函数;反之亦然。

extern “C”这个声明的真实目的是为了实现C++与C及其它语言的混合编程

应用场合

C++代码调用C语言代码、在C++的头文件中使用

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

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);     //注:写成extern "C" int add(int , int ); 也可以
#endif

/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
 return x + y;
}

// c++实现文件,调用add:cppFile.cpp
extern "C"
{
 #include "cExample.h"        //注:此处不妥,如果这样编译通不过,换成 extern "C" int add(int , int ); 可以通过
}

int main(int argc, char* argv[])
{
 add(2,3);
 return 0;
}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C"{}。

在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif

//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
 return x + y;
}

/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
 add( 2, 3 );
 return 0;
}

转载请注明作者Jason Ding及其出处

Github主页(http://jasonding1354.github.io/)

CSDN博客(http://blog.csdn.net/jasonding1354)

简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)

=========================================================================================================

extern“C”的使用方式
如果程序员希望调用其他程序设计语言(尤其是C)写的函数,那么,调用函数时必须告诉编译器使用不同的要求。例如,当这样的函数被调用时,函数名或参数排列的顺序可能不同,无论是C++函数调用它,还是用其他语言写的函数调用它。
程序员用链接指示符(linkage directive)告诉编译器,该函数是用其他的程序设计语言编写的,链接指示符有两种形式:既可以是单一语句(single
statement)形式,也可以是复合语句(compound statement)形式


//单一语句形式的链接指示符
extern "C" void exit(int);
 
//复合语句形式的链接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
 
//复合语句形式的链接指示符
extern "C" {
#include <cmath>
}

链接指示符的第一种形式由关键字extern后跟一个字符串常量以及一个“普通”的函数声明构成。虽然函数是用另外一种语言编写的,但调用它仍然需要类型检查。例如,编译器会检查传递给函数exit()的实参的类型是否是int,或者能够隐式地转换成int型。
多个函数声明可以用花括号包含在链接指示符复合语句中,这是链接指示符的第二种形式。花招号被用作分割符,表示链接指示符应用在哪些声明上。在其他意义上该花括号被忽略,所以在花括号中声明的函数名对外是可见的,就好像函数是在复合语句外声明的一样。例如,在前面的例子中,复合语句extern "C"表示函数printf()和scanf()是在C语言中写的函数。因此,这个声明的意义就如同printf()和scanf()是在extern
"C"复合语句外面声明的一样。
当复合语句链接指示符的括号中含有#include时,在头文件中的函数声明都被假定是用链接指示符的程序设计语言所写的。在前面的例子中,在头文件<cmath>中声明的函数都是C函数。
链接指示符不能出现在函数体中,下列代码段将会导致编译错误:

int main()
{
//错误:
链接指示符不能出现在函数内
extern "C" double sqrt( double );
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}

如果把链接指示符移到函数体外,程序编译将无错误:

extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}

但是,把链接指示符放在头文件中更合适。在那里,函数声明描述了函数的接口所属。
 
在这里我们只看到为C语言提供的链接指示:extern "C"。extern
"C"是惟一被保证由所有C++实现都支持的。每个编译器实现都可以为其环境下常用的语言提供其他链接指示。例如extern "Ada"可以用来声明是用Ada语言写的函数,extern
"FORTRAN"用来声明是用FORTRAN语言写的函数,等等…因为其他的链接指示随着具体实现的不同而不同,所以建议读者查看编译器的用户指南,以获得其他链接指示符的进一步信息。
 

深入理解extern "C"

要明白为何需要使用extern "C",还得从C++中对函数的重载处理开始说起。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( 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。
 

比如下面的一段简单的函数,我们看看加入和不加入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++ primer》3th http://tech.163.com/06/0118/09/27O66HCC0009159Q.html http://dev.yesky.com/72/3270072.shtml http://soft.ccw.com.cn/programing/other/htm2008/20080905_496705.shtml http://www.cnblogs.com/xulei/archive/2006/11/12/558139.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: