extern介绍

C程序由一组对象组成,这些对象包括程序中所使用的变量和实现特定功能的函数。变量可以分为函数内部定义、使用的变量和函数外部定义的变量,通常情况下,把函数内部定义、使用的变量称为内部变量或局部变量,而将在函数外部定义的、供许多函数所使用的变量称为外部变量,一般情况下,也可以称为全局变量。
  
  由于C语言不答应在一个函数中定义其他函数,因此函数本身是外部的。一般情况下,也可以说函数是全局函数。
  在缺省情况下,外部变量与函数具有如下性质:所有通过名字对外部变量与函数的引用(即使这种引用来自独立编译的函数)都是引用的同一对象(标准中把这一性质称为外部连接)。
  
  由于外部变量是可以全局访问的,这就为在函数之间交换数据提供了一种可以替代函数变元欲返回值的方法。任何函数都可以用名字来访问外部变量,只要这个名字已在某个地方做了说明或定义。
  
  假如要在函数之间共享大量的变量,那么使用外部变量要比使用一个长长的变元表更方便、有效。然而,也可能导致程序在各个函数之间产生太多的数据联系。
  
  外部变量的用途还表现在它们比内部变量有更大的作用域和更长的生存期。内部自动变量只能在函数内部使用,当其所在函数被调用时开始存在,当函数退出时消失。而外部变量是永久存在的,他们的值在从一次函数调用到下一次函数调用之间保持不变。因此,假如两个函数必须共享某些数据,而这两个函数都互不调用对方,那么最为方便的是,把这些共享数据作为外部变量,而不是作为变元来传递。
  
  1、外部变量的定义和使用
  
  根据C语言标准,在程序的所有源文件中,外部变量只能被定义一次,否则会导致重复定义的编译错误。
  
  1.1 外部变量的定义与声明
  
  变量声明用于通报变量的性质(主要是变量的类型),而变量定义则除此之外还引起存储分配。假如在函数的外部包含如下说明:
  
   int VarDesc;
  char Array[MAXVAL];
  那么这两个说明定义了外部变量VarDescArray,并为之分配存储单元,同时也用作供源文件其余部分使用的说明。另一方面,如下两行:
  
   extern int VarDesc;
  extern char Array[];
  为源文件剩余部分声明了VarDesc是一个int 类型的外部变量,Array是一个char数组类型的外部变量(数组大小在其他地方确定),但这两个声明并没有建立变量或为它们分配存储单元,其中要害字extern表明该外部变量在其他地方被定义。
  
  根据C语言标准,外部变量虽然只能在某个文件中定义一次,但其作用域则是从其声明处开始一直到其所在的被编译的文件的末尾。因此其他文件可以通过extern说明来访问它。
  
  1.2 外部变量的使用方式
  
  假如外部变量被不同的函数所引用,并且这些函数没有集中在一个源文件中,而是分布在不同的源文件中,那么函数在引用这些外部变量时,必须采取先声明再使用的方式,否则,在编译时会导致重复定义的编译错误。
  
  若在多个文件的多个函数中引用外部变量,就需要在这些函数中重复声明外部变量。这种方式可以解决编译问题,但是代码不够简洁。因此,在实际的编程中,大都采取了将外部变量统一定义在一个C源文件中,这个C源文件一般被称为global.c,然后在对应的头文件中,一般为global.h,声明外部变量,最后在需要引用外部变量的源文件中使用#include "global.h"的方式,函数就可以引用所有的外部变量。因此,一般情况下,global.c内容为:
  
   #include "global.h"
  
  /* for example, define two vars */
  int VarDesc;
  char Array[MAXVAL];
  /* other external var define */
  在对应的global.h头文件的内容则为:
  
   #ifndef _GLOBAL_H /* please insure _GLOBAL_H unique */
  #define _GLOBAL_H /* avoid quotation iterativly */
  
  #ifdef __cplusplus
  extern "C" {
   #endif /*__cplusplus */
  
   /* for example, declare two vars */
   extern int VarDesc;
   extern char Array[];
  
   /* other external vars declaration */
  
   #ifdef __cplusplus
  }
  #endif /* __cplusplus */
  #endif /* _GLOBAL_H */
  此外,在实际的编程中,这两个文件头部还应当有公司copyright声明、文件功能说明、版本说明、创建、修改历史等。
  
  2、函数的定义和使用
  
  根据C语言标准,函数只能被定义一次,而且在函数中,不能再定义函数,因此函数本身是外部的。
  
  2.1 函数的定义与声明
  
  定义函数是给出函数体的函数描述。一个函数只有在声明之后才能被引用。函数声明中,需给出函数名、返回类型、参数列表等。
  
  函数的作用域从其声明处开始一直到其所在的被编译的文件的末尾,假如一个函数在定义之前就要使用到,或者这个函数定义在与所要使用它的源文件不相同的源文件中,那么就需要在使用该函数前,使用要害字extern声明该函数,但由于函数默认是external的,因此函数声明前的extern可以省略,这也是标准库函数的头文件中,函数声明前没有extern的原因,但在实际的编程中,一般不推荐这样做,应当在函数声明前加上extern
  
  2.2 函数的组织和使用
  
  假如某个函数需要引用另一个函数,则需要在引用该函数前声明被引用的函数,否则可能会导致函数未定义错误。
  
  为了避免被引用函数的重复声明和方便函数的引用,在实际的编程之中,采用在对应的头文件中,统一声明函数的方式。需要引用某一个函数时,只需要在该函数的定义源文件中包含被引用函数的头文件即可。
  
  在实际的编程中,在头文件中声明函数,即声明函数原型,在对应的C源文件中,定义函数及其实现代码。因此,函数说明的头文件内容和格式为:
  
  
   #ifndef _FUNCNAME_H /* please insure _ FUNCNAME _H unique */
  #define _ FUNCNAME _H /* avoid quotation iterativly */
  
  #ifdef __cplusplus
  extern "C" {
   #endif /*__cplusplus */
  
   /* for example, declare functions */
   extern int func (int, int, int);
   /* of course, maybe declare functions like below
   * int func (int, int, int);
   */
   /* other functions declaration */
  
   #ifdef __cplusplus
  }
  #endif /* __cplusplus */
  #endif /* _ FUNCNAME _H */
  而在对应的C源文件内容为:
  
   #include " funcname.h"
  
  /* for example, define two vars */
  int func (int a, int b, int c)
  {
   // functions body
  }
  
  /* other functions define */
  在实际的函数头文件和定义文件中还应当包含相应的头文件等,以及公司copyright声明、文件功能说明、版本说明、创建、修改历史等。对于具体的函数,还应当有函数说明、输入参数说明、返回说明、例外等。
  
  3、静态外部变量和函数
  
  假如某外部变量和函数仅供它们各自所在的源文件中的函数使用,而不能被其他函数访问,那么就必须使用static要害字定义外部变量和函数。static说明适用于外部变量与函数,用于把这些对象的作用域限定为被编译源文件的剩余部分。通过外部static对象,可以把一些外部变量和函数隐藏在某个源文件中,使得这些外部变量和函数仅仅可以被该源文件使用和共享,但不能被该源文件之外的函数所引用。
  
  另外,static说明也可以用于说明内部变量。内部静态变量就像自动变量一样局部于某一个特定函数,只能在该函数中使用,但与自动变量不同的是,不管其所作函数是否被调用,它都是一直存在的,而不像自动变量那样,随着所在函数的调用与退出而存在与消失。换而言之,内部静态变量是一种只能在某一特定函数中使用的但一直占据存储空间的变量。
  
  一般情况下,为保证静态外部变量和函数能够被本源文件的函数所引用,需要在该源文件的所有函数之前定义静态外部变量和函数。那么,该C源文件的内容为:
  
   #include “funcname.h”
  
  /* for example, define one static external var and one static function */
  static int VarName = 0;
  static int function (int a)
  {
   // function body
  }
  /* other static vars and functions */
  
  /* for example, define one function */
  char func (char*, char)
  {
   VarName = 5;
   function (VarName);
   // other function body
  }
  
  /* other functions define */

 

时常在cpp的代码之中看到这样的代码:

 

#ifdef __cplusplus

extern "C" {

#endif

 

//一段代码

 

#ifdef __cplusplus

}

#endif

  这样的代码到底是什么意思呢?首先,__cpluspluscpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{}处理其中的代码。

 

  要明白为何使用extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++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;

}

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

 

  总结

 

CC++对函数的处理方式是不同的.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。

 

__declspec(dllexport)

我相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:

不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。

初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量这个是什么意思??

那我就来试验一下,假定,你在DLL里只导出一个简单的类,注意,我假定你已经在项目属性中定义了
SIMPLEDLL_EXPORT
SimpleDLLClass.h

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

class DLL_EXPORT SimpleDLLClass
{
public:
SimpleDLLClass();
virtual ~SimpleDLLClass();

virtual getValue() { return m_nValue;};
private:
int m_nValue;
};
SimpleDLLClass.cpp

#include "SimpleDLLClass.h"

SimpleDLLClass::SimpleDLLClass()
{
m_nValue=0;
}

SimpleDLLClass::~SimpleDLLClass()
{
}
然后你再使用这个DLL类,在你的APPinclude SimpleDLLClass.h时,你的APP的项目不用定义 SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不会存在了,这个时候,你在APP中,不会遇到问题。这正好对应MSDN上说的__declspec(dllimport)定义与否都可以正常使用。但我们也没有遇到变量不能正常使用呀。那好,我们改一下SimpleDLLClass,把它的m_nValue改成static,然后在cpp文件中加一行

int SimpleDLLClass::m_nValue=0;
如果你不知道为什么要加这一行,那就回去看看C++的基础。改完之后,再去LINK一下,你的APP,看结果如何,结果是LINK告诉你找不到这个m_nValue。明明已经定义了,为什么又没有了??肯定是因为我把m_nValue定义为static的原因。但如果我一定要使用SingletonDesign Pattern的话,那这个类肯定是要有一个静态成员,每次LINK都没有,那不是完了?如果你有Platform SDK,用里面的Depend程序看一下,DLL中又的确是有这个m_nValue导出的呀。
再回去看看我引用MSDN的那段话的最后一句。那我们再改一下SimpleDLLClass.h,把那段改成下面的样子:

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
LINK,一切正常。原来dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓。

_declspec(dllexport)_declspec(dllimport)的区别

_declspec(dllexport)_declspec(dllimport)

都是DLL内的关键字,即导出与导入。他们是将DLL内部的类与函数以及数据导出与导入时使用的。主要区别在于,dllexport是在这些类、函数以及数据的申明的时候使用。用过表明这些东西可以被外部函数使用,即(dllexport)是把DLL中的相关代码(类,函数,数据)暴露出来为其他应用程序使用。使用了(dllexport)关键字,相当于声明了紧接在(dllexport)关键字后面的相关内容是可以为其他程序使用的。而 dllimport关键字是在外部程序需要使用DLL内相关内容时使用的关键字。当一个外部程序要使用DLL内部代码(类,函数,全局变量)时,只需要在程序内部使用(dllimport)关键字声明需要使用的代码就可以了,即(dllimport)关键字是在外部程序需要使用DLL内部相关内容的时候才使用。(dllimport)作用是把DLL中的相关代码插入到应用程序中。

_declspec(dllexport)_declspec(dllimport)是相互呼应,只有在DLL内部用dllexport作了声明,才能在外部函数中用dllimport导入相关代码。实际上,在应用程序访问DLL时,实际上就是应用程序中的导入函数与DLL文件中的导出函数进行链接。而且链接的方式有两种:隐式迎接和显式链接。

隐式链接是指通过编译器提供给应用程序关于DLL的名称和DLL函数的链接地址,面在应用程序中不需要显式地将DLL加载到内存,即在应用程序中使用dllimport即表明使用隐式链接。不过不是所有的隐式链接都使用dllimport

显式链接刚同应用程序用语句显式地加载DLL,编译器不需要知道任何关DLL的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值