c语⾔调⽤头⽂件函数要定义吗,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到makefi
le 中指定的那个可以执⾏⽂件中。通常,编译器会在每个.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"c语言struct头文件
#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⽂件,都不会出现重定义。
当然,这不仅仅限于结构的定义。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论