extern的使用详解(多文件编程)——C语言

extern——关键字

extern是C语言中的一个关键字,一般用在变量名前或函数名前,作用是用来说明“此变量/函数是在别处定义的,要在此处引用”,extern这个关键字大部分读者应该是在变量的存储类型这一类的内容中

遇到的,下面先分析C语言不同的存储类型

在C语言中变量和函数有数据类型存储类型两个属性,因此变量定义的一般形式为:存储类型 数据类型 变量名表;

C语言提供了一下几种不同的存储类型:

(1)  自动变量(auto)

(2)  静态变量(static)

(3)  外部变量(extern)

(4)  寄存器变量(register)

(上面的auto、static、extern、register都是C语言的关键字),这里只分析extern关键字的使用

外部变量(全局变量)extern----全局静态存储区

标准定义格式:extern 类型名 变量名;

如果在所有函数之外定义的变量没有指定其存储类别,那么它就是一个外部变量,它的作用域是从它的定义点到本文件的末尾(在单个源文件中的确是这样,如果有多个源文件,全局变量的作用范围不是从变量定义处到该文件结尾,而是在其他文件中也有效),但是如果要在定义点之前或者其他文件中使用它,那么就需要使用关键字extern对其进行声明(注意不是定义,编译器并不为其分配内存)

Tips:

定义:表示创建变量或分配存储单元

声明:说明变量的性质,但并不分配存储单元

extern int i; //是声明,不是定义,没有分配内存
int i; //是定义

如果在声明的时候给变量赋值,那么就和去掉extern直接定义变量赋值是等价的

extern int a = 10;//尽量不要写这种定义方式
int a = 10;//上述两条语句等价

(注意上面的不同语句对声明和定义的区分,对源文件中的局部变量来说是成立的(也就是.c文件),而对于源文件中的全局变量(外部变量)int a和在头文件中的int a就不能用上面的语句来解释声明和定义的区别)

补充:定义和声明的一个小坑,对于int a;来说,在源文件中,如果是全局变量的话就是声明,如果是局部变量的话就是定义

全局变量:

文章前面提到过一句话:如果在所有函数之外定义的变量没有指定其存储类别,那么它就是一个外部变量,意思就是这里的int a;等价于extern int a;相当于声明,声明是可以多次的

局部变量:

 

文章中只提到了extern(外部变量),这里补充下auto(自动变量),由于auto(自动变量)极为常用,所以C语言把它设计成缺省的存储类型,即auto 可以省略不写,在main函数内部的变量int a也是局部变量,

相当于auto int a;相当于定义,定义只能一次

 

谨记:声明可以多次,定义只能一次

外部变量保存在静态存储区内,在程序运行期间分配固定的存储单元,其生存期是整个程序的运行期,没有显式初始化的外部变量由编译程序自动初始化为0(extern说明符来扩展全局变量的作用域,

可以将全局变量的作用域扩展到其他文件,但不能限制全局变量的作用域

extern作用于函数名和变量名时的区别:

读者应该会发现,函数声明时并没有使用 extern 关键字,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有extern 都是一样的

但是作用于变量名时extern关键字就不是可有可无的了,全局变量在外部使用声明时,extern关键词是必须的,如果变量无extern修饰且没有显式的初始化,就成为了变量的定义,因此此时必须加extern,

全局变量在不指定初值时会自动初始化为0

多文件编程

C语言代码是由上到下依次执行的,不管是变量还是函数,原则上都要先定义再使用,否则就会报错。但在实际开发中,经常会在函数或变量定义之前就使用它们,这个时候就需要提前声明(extern)

头文件中包含的都是函数声明,而不是函数定义

最好不要在头文件中定义变量,例如全局变量

这里的int a是个全局变量的定义,所以如果这个头文件被多次引用的话,a会被重复定义,这显然是不允许的,下面举一个简单多文件编程的例子

从上面的四张图可以看出,一共用了3个文件,两个c文件(源文件)一个h文件(头文件),我在my.c中写了一个函数名为Max的函数,在头文件中声明了这个Max函数,在有main的这个test1.c这个

源文件中用#include "my.h"包含了这个头文件,然后在函数中就可以直接使用Max函数了(注意头文件中只是声明,没有定义,而且不建议在头文件中定义函数,会引起很多不必要的麻烦

在《高质量C/C++编程指南》一书中,对此也有说明:

【建议1-2-1】头文件中只存放“声明”,而不存放“定义”。

在C++语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数。这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利。建议将成员函数的定义分开,不论该函数体有多么小。

【建议1-2-2】不提倡使用全局变量,尽量不要在头文件中出现现象 extern int value 这类声明。

作者:奄奄不息 
原文:https://blog.csdn.net/qq_41209741/article/details/84108962 

问题一:如果在my.c这个文件中定义了一个全局变量,想要在main函数中使用,该怎么做呢?

方法一:在含有main函数的源文件test1.c中加extern声明

运行结果:

方法二:在头文件中my.h中对my.c中的全局变量进行声明,再在test1.c中include头文件my.h

运行结果:

防止C语言头文件被重复包含

头文件包含命令 #include 的效果与直接复制粘贴头文件内容的效果是一样的,预处理器实际上也是这样做的,它会读取头文件的内容,然后输出到 #include 命令所在的位置,头文件包含是一个

递归(循环)的过程,如果被包含的头文件中还包含了其他的头文件,预处理器会继续将它们也包含进来;这个过程会一直持续下去,直到不再包含任何头文件,这与递归的过程颇为相似,

递归包含会导致一个问题,就是重复引入同一个头文件,重复引入同一头文件有什么问题呢,当你在头文件中定义变量或者函数时(注意是定义不是声明,多次声明是没有问题的)多次引入头文件就会报“变量被多次定义”的错误

下面来还原这种错误:

上面一共使用了3个头文件k1.h、k2.h、my.h在k1.h和k2.h中都包含了my.h,而且在my.h中有一个全局变量int k = 10,在test1.c中包含了上述三个头文件

编译结果

解决方法:

方法一:使用条件编译#ifndef(如果读者对条件编译不了解,可以先看一下这一篇随笔预处理命令使用详解----#if、#endif、#undef、#ifdef、#else、#elif

解释一下上面的条件编译语句,如果_MY_H这个宏名没有被定义,那么定义_MY_H这个宏名,并且定义全局变量int k = 10,这样做就避免了头文件的重复包含引起的,变量或函数的重复定义问题

我们可以在stdio.h这个“标准输入\输出头文件”中看到类似的用法

格式就像下面这个样子:

#ifndef _INC_STDIO
#define _INC_STDIO
/* 头文件内容 */
#endif

这种宏保护方案使得程序员可以“任性”地引入当前模块需要的所有头文件,不用操心这些头文件中是否包含了其他的头文件

但也不是没有缺点

#ifndef的方式依赖于宏名不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含

缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况

方法二:使用#pragma once

#pramgma once是微软编译器独有的,也是后来才有的,所以知道的人并不是很多,用的人也不是很多,因为他不支持跨平台。如果你想写跨平台的代码,最好使用条件编译,如果想使用#pragma once,

只需在头文件开头加上#pragma once即可

 

#pragma带来的好处是:你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题,坏处也有,#pragma once是由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件,如果某个头文件有多份拷贝,这个方法就不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正

总结:1.#ifndef 由语言支持所以移植性好

    2.#pragma 可以避免名字冲突 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐