extern C小结

 

名词解释

1.extern

  extern翻译为外部的,用在变量或函数之前,使其可见范围拓宽,这就是为啥叫extern吧。那它是用来干嘛的呢?当用在变量或函数之前,提示编译器到其他模块寻找其定义。

  举个例子:

  在源文件A里定义的函数,在其它源文件里是看不见的(即不能访问)。为了在源文件B里能调用这个函数,应该在B的头部加上一个外部声明:
     (extern)   函数原型;   
    这样,在源文件B里也可以调用那个函数了。  

 

2.声明与定义

  声明(declaration)仅仅是声明有这么个东西,但是并没有涉及到内存分配等动作,这些动作在其他地方做,那么它是不是就没什么作用了呢,相反,它还是扮演了很重要的角色,首先它声明了一个变量或函数的类型,让程序知道某个变量的类型,或者一个函数的参数及返回值类型。

  定义(definition)可以理解为声明的超集,也可以把声明理解为定义的子集,定义的同时进行了内存的分配,所以声明可以进行多次,但定义就只能做一次。

 

要点讲解

1.extern 函数

  这个会比较简单,当你定义一个函数时,如

  Int fun(void)

  编译器会默认它为

  extern int fun(void)

  所以在函数之前加或者不加extern区别不大。

  从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int funint mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int funint mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

附上实例:

//file_01.h

int add(int, int);//显然这里是一个全局函数,这样才具有被extern调用的属性。

//file_01.c

#include "file_01.h"
int add(int a, int b)
{
  return a + b;
}

//main.c

#include <stdio.h>
int main(int argc, char** argv)
{
int a = 4;
int b = 5;
extern int add(int, int);//这一行可以放在调用之前的任何地方,extern也是可以省略的
int c = add(a, b);
printf("%d\n", c);
} 

2.extern 变量

  简单点讲:

  extern int var;//这个就是一个声明,可以进行多次

  int var;//这个就是一个定义

  extern int var = 0;//这个不仅是一个声明,同时也是一个定义

  举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明extern int v,还取决于变量v本身是能够被引用到的。这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

附上实例:

//file_01.h

extern int g_var;
/*此处就是一个声明,定义放在.c文件中实现,这样的做法就类似于函数了,只不过因为函数是默认带extern的,
所以平时并没有注意而已,extern如果忽略,那么就相当于在.h文件就定义了,那么后面就直接g_var = 9,相当于赋值(不是初始化),
不过有warning。*/

//file_01.c

#include "file_01.h"

int g_var = 9;//此处是实际的定义了,并且初始化(应该不是赋值)。

//main.c
#include <stdio.h>

extern int g_var;
/*此处extern可以省略会比较神奇,所以省略之后仍然是声明
可以做个简单验证,如果此处int g_var = 0;则会出现重定义错误。
*/

int main(int argc, char** argv)
{
printf("%d\n", g_var);
}

3.extern 数组& extern 指针

  一个源文件里定义了extern char a[] = “abcd”;

  可以在另一个文件里进行声明:extern char* a;吗?

附上实例:

//file_01.h

extern char g_var[];

//file_01.c

#include "file_01.h"

char g_var[] = "abcd";

 
//main.c

#include <stdio.h>

//extern char* g_var;
//如果用指针形式,运行时会出现段错误
extern char g_var[];
/*这里如果两行同时有效,则报错误:
main.c:7: error: conflicting types for ‘g_var’
main.c:6: error: previous declaration of ‘g_var’ was here
从错误上来看,显然两次声明出现了类型冲突,说明编译器并没有把char*和char[]等同看待,
不信把两行都声明成char*或者char[]就能顺利通过编译了。
*/

int main(int argc, char** argv)
{
 printf("%s\n", g_var);
}
 

  那么上面用指针形式报段错误到底是怎么回事呢?


上面用指针形式,直接段错误,说明连接器根本就没把这里的g_var和file_01.c中定义的g_var当成一回事,
它们是两种类型,奇怪的是,如果上述用指针形式,编译能通过,说明编译器认为g_var是已经定义过的变量,
就是file_01.c中定义的那个,但是运行的时候段错误,到底咋回事呢,原来g_var直接被赋予了一个由数组中内容组成的值,
因为原来这里的值是abcd,是被当成char[]来解释的,即g_var等于数组的首地址,而现在要被当成char*来解释了,所以很显然,
那就等价于g_var = “abcd”,即g_var = 0x64636261,否则如果按正常的思路来讲应该是先对g_var初始化,然后再对其赋值,
值为数组首地址地址,但是现在没有单独初始化变量g_var的动作,或者说数组初始化的时候这个变量就已经有了,
说到底是数组和指针的内部结构不一样导致的。如果非要用这种指针形式来打印的话,也最多能打印数组中的前四个字节,
因为指针变量的大小就是四个字节,可以这样打印

unsigned db = (unsigned)g_var;
char* ptr = (char*)(&db);
printf("%s\n", ptr);

 

4.extern C

C++中调用C库函数,就需要在C++程序中用extern C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

 

posted @ 2016-05-18 15:35  Geeken  阅读(158)  评论(0编辑  收藏  举报