C语言中头文件是如何包含的?也说重定义


随着代码越写越长,一个源文件的体制越来越臃肿。于是提倡将代码写到不同的多个源文件中去。将代码写到多个源文件中去就会遇到各个源文件中函数与变量的调用规则问题。

通常人们习惯性的把宏定义、结构体、联合体、枚举、外部变量和外部函数声明等写入到头文件.h中去,而把函数的声明、变量定义等写入到.c文件中去。当某一.c源文件需要调用某一函数的时候,只要将包含这个函数声明的头文件包含到本文件中来就可以了。如a.h中声明了a.c中定义的fun()函数,而b.c需要fun()函数,只需要#include ”a.h”就可以了。B.c怎么找到fun()的呢,难道a.c和a.h间通过同名建立了一种联系,b.c会直接到a.c中去寻找fun()?

很多时候,人们习惯性的把一个源文件中的宏定义、结构体、联合体、枚举、外部变量和外部函数声明等写入到一个同名的头文件中去。这给人的错觉就是同名使得两者被关联起来,在b.c包含a.h时,会自动去a.c中去寻找fun()函数。其实不然。

再给一个例子,如a.h中声明了b.c中定义的fun()函数,而c.c需要fun()函数,只需要#include”a.h”就可以了。这个例子就说明,不是同名的头文件也可以用来声明源文件中的函数等。那怎么通过头文件寻找到函数定义呢?

原理要从编译器工作过程说起。编译器工作过程分为4步骤:

 

1预处理阶段 :以c源文件为单位,处理宏替换、条件编译和文件包含。预处理本质上就是代码文本的替换。拿头文件包含来说,就是把包含的头文件中的所有代码copy到源文件中去。于是就形成了一个“中间c文件”。
2语法分析阶段 :c语言语法分析,你懂得。
3编译阶段:以“中间c文件”为单位,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件 (.obj文件)。编译阶段不会去寻找用到的头文件中的函数的定义,只是按照一定规格转换代码格式为二进制。
4连接阶段:将上一步成生的各个目标文件,根据一些参数,连接生成最终的可执行文件。主要的工作就是重定位各个目标文件的函数,变量等。

 

我们在b.c或c.c中用#include “a.h”实际上是引入相关声明,使得编译可以通过,程序并不关心实现是在哪里,是怎么实现的。源文件编译后成生了目标文件(.o或.obj文件),目标文件中,这些函数和变量就视作一个个符号。在link的时候,需要在makefile里面说明需要连接哪个.o或.obj文件(在这里是b.c生成的.o或.obj文件),此时,连接器会去这个.o或.obj文件中找在c.c中实现的函数,再把他们build到makefile中指定的那个可以执行文件中。通常,编译器会在每个.o或.obj文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了。因此,如果在几个不同源文件中都包含了同一个头文件,该头文件中的某些变量默认地就会被编译超过一次,链接的时候就会提示“redefined”。这也就解释了重定义一般不会在编译阶段出错,而会在链接阶段出错。

 

总结出来一句话:源文件调用#inlude头文件中的函数不是通过直接查找同名源文件找到的,而是通过逐个查找已经编译好的.o或者.obj找到的。但是还是建议使用同名的源文件和头文件,这样便于形成模块化,使代码清晰易懂,便于打理。

编译器的编译过程告诉我们另外一个写头文件需要注意的地方,头文件的预编译在插入有用代码的同时也会插入大量不需要的东西,一个头文件被多个源文件包含还会出现重定义的问题。这些都是需要注意的。插入了不需要的东西在很大程度上是不可避免的,但是精简了代码,看起来简洁了不少。我们可以把那些经常在一起使用的宏定义、声明等放在一个头文件中,减少不必要的公用。关于重定义,可以通过#ifndef #define #endif来解决。下面以一个例子来说明。

 

假设有三个文件

 node.h //定义节点

list.h //对链表的操作函数

test.c //测试函数

包含关系如下:

list.h中

#include"node.h"

test.c中

#include"list.h"

#include"node.h"

#include ... 省略其它必要的头文件

使用命令编译

$gcc -o testtest.c

编译时,会出现错误:XXX重定义

为什么呢?

1)test.c中包含了node.h,因为node.h是定义结构的文件,而且已经被list.h包含了,所以这里node.h会预编译两次,出现重定义!

所以,可以去掉test.c中的头文件node.h即可

2)修改node.h,避免重定义,这种方法也是推荐的方法

#ifndef _NODE
#define _NODE
typedef struct node{
int x;
struct node*next;
}NODE;
#endif


 

使用一个标记变量_NODE来表示NODE结构已经被定义了,将定义过程包含在#ifndef~#endif中,这样,不管包含多少次node.h文件,都不会出现重定义。

当然,这不仅仅限于结构的定义。

  • 13
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值