名词解释
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 fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int 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 ", 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 ", 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 ", 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 ", ptr);
4.extern “C”
在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。