工大后院

 找回密码
 加入后院

扫一扫,访问微社区

QQ登录

只需一步,快速开始

搜索
查看: 1803|回复: 3

[转帖] 关于C中 define 的一些问题

[复制链接]
发表于 2004-10-27 21:58 | 显示全部楼层 |阅读模式
学过C语言的人大概都知道,编写C语言的程序时候,除了程序文件,多半还要包括头文件,也就是我们印象中的.h文件。头文件里到底该放些什么呢?

刚开始学编程的那会儿,就知道头文件里放的东西应该是在多个程序里用到的,那时对一个程序可以实现在多个文件里还没有什么感觉,于是按照自己的“复用逻辑”,把一大堆的东西都塞进了头文件。记得以前曾经编写一个DOS程序,因为程序里用到鼠标,于是写了一个头文件,把所有鼠标相关的函数放了进去。那个程序只有一个程序文件,于是程序运行得很好,我也没有多想,头文件里到底应该放些什么进去。

真正让我开始考虑这个问题的是一次课程设计。那次准备编一个小游戏,用的是VC。由于VC下在一个project下管理多个程序文件比较容易,于是我采用了多程序文件,麻烦开始了。采用了和以前同样的思维习惯,我把一堆东西塞到了头文件中。编那个程序的时候,我把自己学来的防止重编译的手法用上了:

#ifndef __XXX_H

#define __XXX_H

……

#endif

曾为此得意洋洋,以为自己的手法很酷!

但结果是,编译不通过。给出的提示:redefine?重定义?我没有啊?我不是防止重编译了吗?怎么还会出这种错?(当时,我根本没弄清楚重定义和重编译的区别)

为此,挠头了好长时间,幸而在网上求得高人帮助顺利解决。后来才知道,关于这个问题,我曾在《C++编程思想》中看到过相关的论述,只因为字太少,没有引起自己足够的重视。于是再读一遍,豁然开朗。


那C语言的头文件中到底该放些什么呢?

说白了,很简单,就是声明(declaration),而在程序文件里放的应该是定义(define)。声明和定义的区别何在?就差一个空间分配,也就是说,声明是不分配空间的,而定义是要分配空间的。胡涂了吧!那我就以自己的错误为反面教材解释一下吧!

我当时犯下的错误就是将许多定义放到了头文件中,比如在头文件中这么写:

char ch;

这就是一个定义。别喊,我知道这个叫声明语句,可它实际上就是一个定义,它之所以称为“声明”,我以为完全是历史上的冤假错案。

让我们看一下C语言的编译过程,如果你在Unix/Linux下编写过程序,你一定知道Makefile。Makefile里经常会有这种东西:

exe : a.o b.o

gcc -o exe a.o b.o

a.o : a.c

gcc -c a.o a.c

b.o : b.c

gcc -c b.o b.c

这就大概反映了一个编译过程,就是由a.c生成a.o,b.c生成b.o,然后由a.o和b.o一起来生成exe。

现在回到我们的问题上,我们在头文件里定义了一个变量。而这个头文件有恰好被a.c和b.c所包含,也就是说两个文件都有这么一句。

#include “header.h” (假设该文件中就包含上面出现的定义)

在C的编译过程,预处理指令是在编译进行前先行处理的。Include预处理指令就是让header.h在a.c和b.c中被展开,也就是说,在预处理完的两个程序中,都包含了

char ch;

你不信的话,可以自己试一下。

Gcc –E a.c > a.i(a.i是自己起的名字)

编译行动继续往下走,二者都生成了自己对应的.o文件,准备最后的连接了。这是编译器发现,在a.o中有一个变量叫ch,在b.o中也有一个变量叫ch,都要自己的空间,给a,b不同意,给b,a也不同意。于是它搞不定了,最后决定向你报告,结果就出现了一个连接错误:重定义。


现在知道问题是怎么出来的了吧!知道了哪里有问题,就要想办法解决问题。

我们的问题是在头文件中放置了定义,而在多个程序文件中包含了这个文件。实际上,我们之所以把这个变量放入头文件,就是因为我们可能在多个地方用到它,这也就是我们对头文件的最基本的认识。我又要多个地方用到,又不能在头文件中定义,那该如何解决呢?

声明是最好的回答。

我知道,从刚才你就想骂我,因为,我把声明语句说成了定义,那声明跑哪去了?真正的声明在C语言中应该这么写:

extern char ch;

都是初学C语言时用了谭老师的那本C语言教材的错,我们都忽略了extern,就知道了个“外部的”,具体怎么用都不清楚。(别废话,说正题!)

这样,我们就声明了一个变量,这才是真正的“声明”。

声明的作用就是告诉计算机介绍一个名字。编译器看到它,就知道有个变量叫ch,但并不为它分配空间,当然,如果真正用到了ch,还是要给它分配空间的,在哪分配?都说不能在头文件了,当然只能在程序文件里了。通常一个实现文件对应着一个目标文件,而生成可执行文件的时候,目标文件们是多对一,于是就不可能出现多个定义了。

上面基本上都是在说变量,其实对于函数也存在着同样的问题,说过了变量问题,函数问题就简单了,你只要知道,如何声明、如何定义就够了。我们通常写函数就是定义,那声明呢?就是我们常说的函数原型。

如果有兴趣,你可以打开C语言一个库文件看看,现在知道为什么里面基本上都是函数原型了吧!


当然,事事没有绝对,我并没有说头文件中绝对不能放“定义”,放也可以,没人会反对你,前提是,你确实知道自己在做什么。


再解释一下,前面提到过的防止重编译的手法。

相信只要写得正规点的头文件中,在文件的开头结尾,你会看到这样的东西:

#ifndef __XXX_H

#define __XXX_H

……

#endif

为什么这么写呢?

先来看看,上面的语意吧!

如果没有定义__XXX_H,就定义它。

文件编译的时候,如果同一个文件被多次包含,第一次遇到这个语句的时候,它显然还没有定义__XXX_H,于是定义它,继续下面的编译。后面再遇到的时候,因为已经定义过__XXX_H了,就直接跳到#endif。中间的东西就不会被再次编译。

你或许想问,我为什么要在一个文件中包含多个头文件,你通常是不会这么干,但你知道你包含的头文件都干了些什么吗?

我的意思是,有可能有这种情况:

你写了两个头文件a.h和b.h,而b.h中包含了a.h。

当你编程的时候,如果同时用到a.h和b.h,所以就这么写了:

#include “a.h”

#include “b.h”

上面已经说过,include指令会在当前位置将程序展开,展开b.h的时候,a.h需要在b.h里展开,于是,a.h被展开两次。

你的本意并不是把a.h处理两次,而实际上如果你不处理一下的话,它就会被处理两次。这种情况在使用标准库的时候会变得非常频繁,因为你通常不会注意每个头文件中还包含了那些文件。

防止重编译的最大的好处是省时,当然,你若不在乎那点时间,别人也没办法:)

如果你的程序还没有解决好声明与定义的问题,把定义写进了头文件,又忘了防止重编译,哈哈,两个麻烦就会一起找到你,具体的情形,自己分析去吧!
发表于 2004-11-27 02:00 | 显示全部楼层
学到东西了.
回复

使用道具 举报

发表于 2004-11-27 15:47 | 显示全部楼层
回复

使用道具 举报

发表于 2004-11-28 10:12 | 显示全部楼层
路过了,支持。。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入后院

本版积分规则

QQ|Archiver|手机版|小黑屋|广告业务Q|工大后院 ( 粤ICP备10013660号 )

GMT+8, 2024-5-16 11:56

Powered by Discuz! X3.5

Copyright © 2001-2024 Tencent Cloud.

快速回复 返回顶部 返回列表