程序员

注册

 

发新话题 回复该主题

C语言头文件被include后都发生了什 [复制链接]

1#

头文件是C语言的一个重要组成部分,这种类型的文件名一般以.h结尾,h表示header,因此被称为“头文件”。头文件里一般存放公开的函数原型,数据类型等内容,其他模块需要使用这些函数或者数据类型时,只需包含相应头文件即可。

相信读者大都使用过C语言的头文件,不过还是有可能对其理解不透彻,这会导致读者在遇到一些问题时不知道如何解决。本文将较为详细的讨论C语言头文件的特点,并在此基础上,分析几个初学者常会跳进的“陷阱”,以及相应的解决办法。

C语言的#include语法

头文件通常与C语言的#include语法配合使用,意为“将头文件内容包含进来”,例如在t.c文件里写下这段C语言代码:

C语言代码

编译器在编译这段C语言代码之前,会有一个“预处理”的过程,在此过程中,stdio.h里的内容被展开到t.c文件里。事实上,在终端输入gcc-E命令即可查看预处理后的C语言代码:

#gcc-Et.c

可见,编译器在预处理阶段会将stdio.h的内容展开到main()函数之前。事实上,如果创建str.h文件,并在其中写入“helloworld\n”,我们甚至可以写出下面这样的C语言代码:

在其中写入“helloworld\n”

输入gcc-E查看编译器预处理后的C语言代码,会发现编译器将str.h文件里的内容“helloworld\n”展开到printf()里了,此时printf(#include“str.h”);等价于printf(“helloworld\n”);,所以编译并执行这段C语言代码,得到如下输出:

#gcct.c#./a.outhelloworld

到这里,相信读者已经发现,在C语言程序开发中,#include实际上就是把头文件里的内容复制到对应的位置。

避免C语言代码重复包含头文件

今天在我的交流群里有个小伙伴在编写C语言程序时,遇到了自己无法解决的错误。为了讨论主题,我把他的问题简化:创建test.h文件,并在其中定义一个全局变量:

//test.h文件intglobal_val=0;

然后创建t1.c文件,使用#include包含该头文件,相应的C语言代码如下,请看:

相应的C语言代码

编译这段C语言代码,小伙伴发现编译器报错了:

global_val被重复定义

错误信息提示变量global_val被重复定义,但是小伙伴查看自己的代码,发现只有test.h里一处定义了变量global_val,这让他很迷惑。

小伙伴会感到迷惑,主要是因为对C语言的“头文件”机制理解不够深入,他认为只有test.h文件一处定义变量global_val,不可能会导致“重复定义”错误的。

实际上,按照我们上面的分析,#include包含头文件并没有什么特别的,它只是将头文件里的内容复制到#include处而已。知道了这一点,再看小伙伴的C语言代码,就一切明了了:他不小心(也有可能故意)包含了test.h文件两次,所以test.h文件里的内容会被赋值到main()函数之前两次,就相当于:

intglobal_val=0;intglobal_val=0;intmain()...

这当然会引发“重复定义”的错误。解决错误的办法很简单,避免头文件被重复包含即可,所以删去一个#includetest.h就可以了。不过,我们能够轻易发现头文件被重复包含,是因为这里的代码很简单。如果C语言代码再复杂一点,或者多几层嵌套,就比较难发现头文件被重复包含了。

例如,test1.h包含了test2.h文件,test2.h文件包含了test.h文件。这种情况下,t.c文件同时包含test1.h和test.h文件,一样会引起test.h文件被重复包含的。

在实际的C语言项目开发中,头文件一般都要加上预编译条件语句,比较常用的有#ifdef,#ifndef等。例如,将test.h文件做如下修改:

将test.h文件做如下修改

上述C语言代码中的#ifndef和#define配合,可避免该头文件在同环境中被重复包含。所以即使t.c文件中写了多次#includetest.h文件,编译器也不会再报错:

编译器也不会再报错

编译并执行这段C语言代码,可得如下输出:

#gcct2.c#./a.outval=1

初学者感到头疼的问题

有的读者知道使用#ifdef等条件编译语句避免头文件在同环境被重复包含,但还是有可能写出有问题的C语言程序。下面这个问题也是群里小伙伴提出的,为了讨论主题,我对其做了精简:小伙伴在test.h文件和t1.c文件的工程基础上,新建了t2.c文件,其中t2.c文件的内容如下:

t2.c文件的内容

显然,t2.c文件也包含了test.h头文件,并使用了其中定义的global_val变量。然后小伙伴在将add_val()函数的原型加入test.h头文件里:

将add_val()函数的原型加入test.h

接着,小伙伴在t1.c文件里调用了add_val()函数,相关C语言代码如下,请看:

相关C语言代码

写好这些C语言代码后,发现编译报错了,依然是重复定义的错误,小伙伴感到非常迷惑。为什么写了预编译语句,还是出现这种错误呢?

还是出现这种错误

答案其实很简单,预编译条件语句仅作用于同一环境。t1.c和t2.c文件属于两个模块,因此#ifndef不能避免test.h文件被t1.c和t2.c同时包含,这就会导致intglobal_val=0;在整个C语言程序中有两处定义,编译器自然会报错。

小结

本文较为详细的介绍了C语言中头文件的性质,并在此基础上,分析了初学者常遇到的两个问题。应明白,在实际的C语言项目开发中,很少有程序员会在头文件里定义全局变量。作为延伸,如果本文中test.h文件里的global_val定义为static变量,那么编译就不会报错了。究竟为什么,以及加上static会带来什么样的变化,留给读者自己思考了。

点个赞再走吧
分享 转发
TOP
发新话题 回复该主题