C/C++头文件使用 #ifndef #define #endif 的原因
背景
在编译的时候,出现“redefine”的错误,最后检查才发现对应的头文件没有写正确的预编译信息:
#ifndef _HeadFileName_H
#define _HeadFileName_H
// 头文件内容
#endif //_HeadFileName_H
添加后,不再报错,然后就思考,这个“#ifndef #define #endif”的作用到底是什么?于是有了此篇文章。
正文
“#ifndef #define #endif”其实是预编译的一种机制。
在我们的C project中,通常由“.c”“.h”文件构成,“.c”文件即是具体的代码,相应的“.h”文件即是对应“.c”文件的说明。
在预编译的时候,编译器会将“.c”包含的所有的“.h”文件内的函数,宏定义,变量信息给汇聚到一个文件内,但能被写入该文件的条件是,所有的这些函数入口,宏定义,变量信息有且只能有一个,因此,当有多个“.c”文件包含同一个“.h”文件时,就会出现重复定义的错误,所以“#ifndef”、“#define”、“#endif”就派上用场了,当编译器第一次预编译一个“.h”文件时,该文件内的宏则还没有被定义,然后编译器则会先定义该宏,并将该“.h”文件内的信息记录,待第二次编译器又预编译到同一个“.h”文件时,该宏定义已经被定义,则编译器直接跳转到“#endif”,略过此包含。
形象点比喻,整个project就是一个由可以实现各种功能的的厂房组成的工厂,每个“.c”文件对应的即是工厂内的每一个厂房,对应的“.h”则挂在对应的厂房外,标明了这个“.c”厂房内有哪些设备,可以实现哪些功能。而另外一些“.c”厂房如果需要用其他厂房的功能,只需要在他的“.h”标示文件内将其他厂房内的“.h”标示文件写在其标示文件内即可,这就是文件包含(相当于在“.h”文件内包含其他“.h”文件);或者直接在他的“.h”文件旁,挂其他厂房的“.h”标示文件(相当于在.c文件包含其他“.h”文件)。
在实际运行时,整个工厂就相当于一个流水线开始运作了,当运作到“A.c”A厂房的时候,在这个环节内,需要使用“B.c”B厂房内的一个功能,这个流水线就会根据A厂房外挂的B厂房的标示文件“B.h”找到B厂房对应功能的工位地址,接着去运行对应的功能。这就是“.c”、“.h”的功能。
预编译阶段,就相当于将这个工厂组合起来的组织者(编译器)实现工厂整合的过程,可是这个组织者比较笨。他会按照顺序巡视所有的厂房,譬如A厂房,然后一看A厂房的“.h”标示板,就会用个文件记下来,A厂房有功能1,功能2。在巡视A厂房的时候,它看到了B厂房的标识板,组织者(编译器)如实记录A厂房还有B厂房的功能3,功能4,然后检查了下A厂房的设备都没问题(C文件内没有语法错误)。接着组织者(编译器)巡视到在B厂房的标识板时,看到B厂房也有功能3,功能4.组织者(编译器)就不干了,就说这个功能A厂房已经有了,不能重复建设,不环保(- -!),项目不能通过验收,回去重做(报redefine的错误)。
于是工厂建造者(我们程序员)就想起来一个方法“#ifndef #define #endif”,和编译器约定了一个规则,如果碰到标示板(“.h”)文件上没有定义对应的头文件的宏,那就定义它,然后将里面的功能给记录下来,以#endif表示结尾。如下所示,
#ifndef _HeadFileName_H
#define _HeadFileName_H
// 头文件内容
#endif //_HeadFileName_H
接着,编译器看到同一个头文件对应的宏定义之前已经定义了,说明这些功能已经记录,编译器就会直接跳转到对应的#endif位置,无视此次包含。由于这些功能此前已经记录,因此不会出现在实际运行时找不到对应的功能入口地址的情况。
以上说的既是多个文件包含同一个头文件,编译器的具体行为。
要强调一点,以上所说的头文件内一定不要定义变量,只能定义宏定义、enmu,函数声明。因为,定义后者,编译器只需要将对应的记号记录到一个文件内即可,定义前者,编译器一定会给它分配实体地址,第二次再来包含的时候,若是在头文件内有变量,有些编译器会无视“#ifndef #define #endif”的约定并再次为它分配地址,然后编译器发现之前已经分配过地址了,还是会报“redefine”的错误。当然只是有些笨的编译器会,已经证实gcc编译器不会有这种笨的处理,但是,为了使自己的代码能兼容更多的的编译器,还是建议不要在“.h”文件内定义变量。
至此,记录完毕。
参考链接
Why use #ifndef CLASS_H and #define CLASS_H in .h file but not in .cpp?;
记录时间:2017-1-6
记录地点:深圳WZ
C/C++头文件使用 #ifndef #define #endif 的原因的更多相关文章
- 头文件中ifndef/define/endif的作用以及#pragma once使用
例如:要编写头文件test.h 在头文件开头写上两行: #ifndef _TEST_H #define _TEST_H//一般是文件名的大写 ············ ············ 头文件 ...
- c/c++头文件中#ifndef/#define/#endif的用法
想必很多人都看过“头文件中用到的 #ifndef/#define/#endif 来防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?头文件被重复引用了,会产生什么后果?是不是所有的头文 ...
- 头文件里面的ifndef /define/endif的作用
c,c++里面,头文件里面的ifndef /define/endif的作用 今天和宿舍同学讨论一个小程序,发现有点地方不大懂······ 是关于头文件里面的一些地方: 例如:要编写头文件test.h ...
- C++头文件为什么要加#ifndef #define #endif
#ifndef 在头文件中的作用 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时 ,就会出现大量“重定义”的错误.在头文件中实用#ifndef #de ...
- 头文件为什么要加#ifndef #define #endif
#ifndef 在头文件中的作用 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时 ,就会出现大量“重定义”的错误.在头文件中实用#ifndef #de ...
- 头文件中的#ifndef/#define/#endif 的作用
在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时,就会出现大量重定义的错误.在头文件中实用#ifndef #define #endif能避免头文件的重定 ...
- #ifndef #define #endif 防止头文件被重复引用
想必很多人都看过“头文件中的 #ifndef/#define/#endif 防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?是不能在不同的两个文件中使用include来包含这个头文件 ...
- 头文件#ifndef #define #endif使用
想必很多人都看过“头文件中的 #ifndef #define #endif 防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?是不能在不同的两个文件中使用include来包含这个头文件 ...
- 头文件种的ifndef/define/endif 是干什么用的
头文件种的ifndef/define/endif 是干什么用的? 答:防止头文件被重复包含.
随机推荐
- 如何在 Java 中正确使用 wait, notify 和 notifyAll(转)
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- [LeetCode] Delete Node in a Linked List 删除链表的节点
Write a function to delete a node (except the tail) in a singly linked list, given only access to th ...
- CoreOS和Docker入门
转载自: http://www.oschina.net/translate/coreos_and_docker_first_steps?cmp CoreOS是一个基于Linux,systemd和Doc ...
- 递推 hdu 1396
给你边长为n的等边三角形 算有几个三角形 z[1]=1; 第N层 z[n] 1 n-1层 z[n-1] 2 2*n-1 个小的 3 新产生的 正的>1的三角形 n*(n-1)/2; ...
- Azure 带宽
Azure带宽与Azure Blob云存储 http://www.cnblogs.com/threestone/p/4497625.html
- WCF、Web API、WCF REST、Web Service比较
原文地址:http://www.dotnet-tricks.com/Tutorial/webapi/JI2X050413-Difference-between-WCF-and-Web-API-and- ...
- 因为多余jar包,所报的错
今天,将项目部署到服务器时,出现了上述的错误.于是就开始找度娘了,找啊找,将项目里面的东西翻来覆去的改,不见效果,还多了些莫名其妙的错,当时就懵逼了.在百度上找到的方法都是说有jar包重复,项目里面的 ...
- C#检验参数合法性公用方法
#region 检验参数合法性,数值类型不小于0,引用类型不能为null,否则抛出异常 /// <summary> /// 检验参数合法性,数值类型不小于0,引用类型不能为null,否则抛 ...
- 浅谈servlet
刚开始接触servlet的时候,其实不是太理解servlet的,后来经过慢慢摸爬滚打式的的学习,有了一点自己的理解. servlet的产生还要从Java和HTTP说起: Java的servletAPI ...
- 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序
3779: 重组病毒 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 224 Solved: 95[Submit][Status][Discuss] ...