关于头文件

提示:文章

文章目录

前言

前期疑问:
本文目标:


一、背景

最近

二、

首先说下头文件,其实头文件对计算机而言没什么作用,她只是在预编译时在#include的地方展开一下,没别的意义了,其实头文件主要是给别人看的。 我做过一个实验,将头文件的后缀改成xxx.txt,然后在引用该头文件的地方用 #include"xxx.txt"

编译,链接都很顺利的过去了,由此可知,头文件仅仅为阅读代码作用,没其他的作用了!

【正例】

a.c内容

#include "b.h" // 符合:通过包含头文件的方式使用其他.c提供的接口
int Func(void)
{
	int i = Foo(); // 这里使用了外部接口
	...
	return g_bar; // 这里使用了外部变量
}

b.h内容

int Foo(void);
extern int g_bar;

b.c内容

int Foo(void)
{
	// 执行某些操作
}
int g_bar;

然后教授开始引导李雷学习C语言工程的概念,也就是说将来的程序越来越大,你横不能把所有的东西都写在 main.c 里面吧,聪明的李雷想了想,非常有道理,于是教授告诉他,要把一些可以复用的代码从 main.c 里面分离出来,这样既不至于让编译器对着一个超大c文件发疯,也方便程序员维护。

聪明的李雷立刻领会了教授的意思,

他把 adjust_money() 函数分离出来了,于是写下了三个文件:

func.h
func.c
main.c

教授看了看,疑惑地问李雷: 你为啥要把 int money 放在头文件里面呢?

李雷说:“我考虑到万一需要直接访问这个变量,所以把它定义为全局变量了。”

教授点点头,说:“这个考虑是没问题的,但你想过没有,你在 func.h 里面定义了这个 money , 会导致你的程序无法连接。”

李雷摇头说:“没问题啊,不信你看。”

李雷得意地看着教授说:“你看,编译没问题啊。”

教授摇头说:“我说的是你没办法连接,不是说你没办法编译。不信你试试看把它连接成一个可执行文件。”

李雷将信将疑地试着连接:

果然,编译器报错了, money 这个变量被重复定义了, 李雷没办法按预期的获得可执行文件。

聪明的李雷这下有点傻了。

教授微笑着说:“看来你对头文件还没有本质的认识。其实你记住一个原则就好了:你每编译一个c文件,其本质就是,编译器将这个c文件所包含的头文件内容按顺序原样添加到这个c文件里面的。”

李雷还是有点懵。

教授说:“现在让我们来看看你编译 main.c 的时候实际上发生了什么。”

我们再来看看 main.c 的内容:

编译器读到第一行, #include ‘func.h’

编译器就在可搜索路径中找到这个 func.h, 然后读取 func.h , 将 func.h 的内容添加进来。

这时在编译器眼中, main.c 其实变成了:

聪明的李雷恍然大悟,说:“我懂了!所以当编译器读到 #include <stdio.h> 的时候,它又继续把 stdio.h 的内容插入到这里来。”

教授点头说:“本质就是这样。那么现在你看, 编译器编译 main.c 的时候,是不是等同于 main.c 里面定义了一个 int money ? ”

李雷说:“是的。”

教授说:“那么你在编译 func.c 的时候,会发生什么呢?”

聪明的李雷这下完全明白了,编译器编译 func.c 的时候,实际上是在编译如下内容:

所以 func.c 里面其实又定义了一次 int money。

所以 main.o func.o 两个obj文件里面都各自有了一次 int money 的定义。

所以当你试图把 main.o func.o 连接成可执行文件的时候, 连接器就会发现有两个 money 变量的定义冲突。

所以正确的做法是什么呢?

教授告诉李雷:“我再说一遍,头文件只做声明,C文件才做定义。所以你的 func.c func.h 分别应该是这样的。”

func.h
func.c

按这样的正确做法,你编译出来的 func.o 里面有 int money 变量的唯一定义, 而 main.o 里面就不会有,这样连接器在连接obj文件的时候就不会出错了。

李雷恍然大悟,但是他又问道:“那么 func.h 里面那句 extern int money 到底起什么作用呢?”

教授给了李雷一巴掌,说:“当然是让编译器在编译其他依赖C文件,比如 main.c 的时候, 知道有 int money 这么一个外部变量可以用,但是具体这个 int money 的定义在哪里,是连接器在连接阶段负责去定位的(在这里就是 func.o 里面),编译器并不负责定位。”

教授又说:“我看你还是没搞懂 编译时期 和 连接时期 的区别。你再去看看资料,把这个问题搞清楚,那么今天的课题你就理解更深刻了。”

杠精出身的李雷又想到一个问题,说:

gang.h

我在这个头文件里面定义了一个 杠精 的结构,为什么就可以呢?

教授大怒,骂道:“我们说的是变量和函数,你狗日的跟我扯类型。类型和宏都是在编译器的生存期的,跟连接期没关系,当然没这个问题。那一堆 #ifndef 不就是为了避免这种结构在编译期的重复定义的吗?”


总结

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值