背景

在编译的时候,出现“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 的原因的更多相关文章

  1. 头文件中ifndef/define/endif的作用以及#pragma once使用

    例如:要编写头文件test.h 在头文件开头写上两行: #ifndef _TEST_H #define _TEST_H//一般是文件名的大写 ············ ············ 头文件 ...

  2. c/c++头文件中#ifndef/#define/#endif的用法

    想必很多人都看过“头文件中用到的 #ifndef/#define/#endif 来防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?头文件被重复引用了,会产生什么后果?是不是所有的头文 ...

  3. 头文件里面的ifndef /define/endif的作用

    c,c++里面,头文件里面的ifndef /define/endif的作用 今天和宿舍同学讨论一个小程序,发现有点地方不大懂······ 是关于头文件里面的一些地方: 例如:要编写头文件test.h ...

  4. C++头文件为什么要加#ifndef #define #endif

    #ifndef 在头文件中的作用 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时 ,就会出现大量“重定义”的错误.在头文件中实用#ifndef #de ...

  5. 头文件为什么要加#ifndef #define #endif

    #ifndef 在头文件中的作用 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时 ,就会出现大量“重定义”的错误.在头文件中实用#ifndef #de ...

  6. 头文件中的#ifndef/#define/#endif 的作用

    在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时,就会出现大量重定义的错误.在头文件中实用#ifndef #define #endif能避免头文件的重定 ...

  7. #ifndef #define #endif 防止头文件被重复引用

    想必很多人都看过“头文件中的 #ifndef/#define/#endif 防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?是不能在不同的两个文件中使用include来包含这个头文件 ...

  8. 头文件#ifndef #define #endif使用

    想必很多人都看过“头文件中的 #ifndef #define #endif 防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?是不能在不同的两个文件中使用include来包含这个头文件 ...

  9. 头文件种的ifndef/define/endif 是干什么用的

    头文件种的ifndef/define/endif 是干什么用的? 答:防止头文件被重复包含.

随机推荐

  1. angularJS(4)

    angularJS(4) 一:angulaJs的作用域scope Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带.scope 是一个 JavaScr ...

  2. channel Golang

    Golang, 以17个简短代码片段,切底弄懂 channel 基础 (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的 ...

  3. font-size 兼容问题

    早年~ 楔子 在为“我的抵扣券”添加  按钮时,为了将文字隐掉,给节点设置了“font-size:0;”,设置后刷一下浏览器,webkit下按钮掉下去了,而其他浏览器(包括IE6/7)都正常: 按理说 ...

  4. [LeetCode] Anagrams 错位词

    Given an array of strings, return all groups of strings that are anagrams. Note: All inputs will be ...

  5. VS2015 使用及插件推荐

      环境搭建与插件推荐 Cpp in Win10 x64 vs2015 + Resharper/Visual Assist X vs2015Pro: Download or see BaiDu 中文专 ...

  6. TCP/IP是一种十一状态

    1.建立连接协议(三次握手) 三次握手过程说明: 1.  在最开始,客户端和服务器都是处于CLOSED状态 2.服务器会创建sockert开始监听,服务器状态LISTEN 3.客户端向服务器端发送SY ...

  7. List [][]

    # -*- coding:utf-8 -*-   L = [     ['Apple', 'Google', 'Microsoft'],     ['Java', 'Python', 'Ruby', ...

  8. 织梦多语言站点,{dede:include filename=''/}引入问题

    织梦模板include插入非模板目录文件出现"无法在这个位置找到"错误的解决办法 以下是dede V55_UTF8 查dede include标签手册 (3) include 引入 ...

  9. C#-WebForm-简单控件

    在HTML中称"元素",添加了"runat='server'"后称控件,后台服务端可以控制 想要后台改变前端的控件,需要先让后台获取前端控件 常用的简单的表单元 ...

  10. delphi 取硬盘号

    function GetVolumeID: string; var vVolumeNameBuffer: ..] of Char; vVolumeSerialNumber: DWORD; vMaxim ...