一、头文件重复包含问题分析
1) 问题重现
举例说明。假设在某个C++ 头文件 或 源文件 中,包含了A.h和B.h两个头文件:
#include "A.h"
#include "B.h"
事实上,在头文件B.h中也包含了头文件A的引用,即:
#include"A.h"
这样在编译这个文件时,因为文件包含了 A.h 这个头文件,编译器展开这个头文件,知道了 A 这个类的定义了,接着展开B.h头文件,而在B.h头文件中也包含了A.h,在此展开A.h,于是类A就重复定义了。
以上就是头文件重复包含问题的重现过程。
简单的理解:
无非就是头文件里有一行
int a=1;
包含两次就变成了:
int a=1;
int a=1;
于是变量重复定义,报错
类,函数同理
而当你写成
#ifndef XXX #define XXX
int a=1;
#endif
包含两次就是
#ifndef XXX #define XXX
int a=1;
#endif
#ifndef XXX #define XXX
int a=1;
#endif
第一次中,由于没有定义XXX,所以做了两件事,定义XXX,然后int a;
第二次中,由于已经定义XXX,所以啥都不做
pragma once是上述方式的简写,好处是再也不会有两个头文件因为使用了同样的XXX而被忽略了
2) 解决方案
2.1采用条件编译
具体的实现如下:
预编译语句:
#ifndef AFX_A_H__E4EC8E17_XXXX_4C73_B589_XXXXC__INCLUDED_
#define AFX_A_H__E4EC8E17_XXXX_4C73_B589_XXXXC__INCLUDED_
class A
{
public:
A();
~A();
};
#endif //AFX_A_H__E4EC8E17_XXXX_4C73_B589_XXXXC__INCLUDED_
//说明:条件编译后这一串字符串主要是为了保证唯一,
//自己可以任意定义,但最好可以包含头文件或类名的信息,这样方便阅读代码。
- 再次编译,当编译器再次展开A.h头文件时,条件预处理指令判断AFX_A_H__E4EC8E17_XXXX_4C73_B589_XXXXC__INCLUDED_没有定义,于是就定义它,然后继续执行,定义了A这个类;
- 接着展开B.h头文件,而事实上在B.h头文件中也包含了A.h,再次展开A.h,这个时候条件预处理指令发现AFX_A_H__E4EC8E17_XXXX_4C73_B589_XXXXC__INCLUDED_已经定义,于是跳转到#endif,执行结束。
- 这样,在此次的编译过程中,A这个类只定义了1次。
2.2 添加杂注 #pragma once
#pragma once是一个比较常用的C/C++杂注,只要在头文件的最开始加入这条杂注,就能够保证头文件只被编译一次。
1. #pragma once概述
#pragma once是编译器相关的,就是说即使这个编译系统上有效,但在其他编译系统也不一定可以,不过现在基本上已经是每个编译器都有这个杂注了。
#ifndef,#define,#endif是C/C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。
2. 具体写法
方式一:
#ifndef _SOMEFILE_H_
#define _SOMEFILE_H_
.......... // 一些声明语句
#endif //_SOMEFILE_H_
方式二:
#pragma once
... ... // 一些声明语句
3. 两者比较
-
#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况。
-
#pragma once则由编译器提供保证:同一个文件不会被编译多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。
-
方式一由语言支持所以移植性好,方式二 可以避免名字冲突
-
#pragma once方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受语言天生的支持,不受编译器的任何限制;而#pragma once方式却不受一些较老版本的编译器支持,换言之,它的兼容性不够好。也许,再过几年等旧的编译器死绝了,这就不是什么问题了。
我还看到一种用法是把两者放在一起的:
#pragma once
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
... ... // 一些声明语句
#endif //__SOMEFILE_H__
看起来似乎是想兼有两者的优点。不过只要使用了#ifndef就会有宏名冲突的危险,所以混用两种方法似乎不能带来更多的好处,倒是会让一些不熟悉的人感到困惑。
二、头文件重复包含的影响
重复包含头文件有以下问题:
- 1.使预处理的速度变慢了,要处理很多本来不需要处理的头文件。
- 2.可能预处理器就陷入死循环了(其实编译器都会规定一个包含层数的上限)。例如C.h包含D.h,D.h又包含C.h的情况,如果不采用防止头文件的重复定义,那么预处理器就会进入死循环了。
- 3.头文件里有些代码不允许重复出现。而重复定义头文件会重复出现一些代码。(虽然变量和函数允许多次声明(只要不是多次定义就行),但头文件里有些代码是不允许多次出现的)。例如:使用typedef类型定义和结构体Tag定义等,在一个程序文件中只允许出现一次。
三、关于条件编译
语法格式:
#ifdef 标志符
程序段1
#else
程序段2
#endif
含义:当定义了标志符则对程序段1进行编译,而没有定义标志符时则编译程序段2。
- 采用条件编译的原因:其实这跟事物具有多样性一样。我们需要对不同的状况采取不同的对策。例如程序的运行平台具有多样性,我们在window平台下编写的程序,可能使用某一个库或者与硬件相关的属性或方法,现在要将我们的程序移植到别的计算机系统上运行的时候,假定为linux系统,那么程序上依赖的库或者有些和硬件相关联的属性和方法不得不更改,那么我们只能在编写程序的时候提高程序的健壮性,此时就需要条件编译语句为我们实现这样的功能。
四、附录[不同编译器或开发环境对应的条件编译指令]
//GCC
#ifdef __GNUC__
§ #if __GNUC__ >= 3 // GCC3.0以上
//Visual C++
#ifdef _MSC_VER(非VC编译器很多地方也有定义)
§ #if _MSC_VER >=1000 // VC++4.0以上
§ #if _MSC_VER >=1100 // VC++5.0以上
§ #if _MSC_VER >=1200 // VC++6.0以上
§ #if _MSC_VER >=1300 // VC2003以上
§ #if _MSC_VER >=1400 // VC2005以上
// Borland C++
#ifdef __BORLANDC__
//UNIX
#ifdef __unix
or
#ifdef __unix__
//Linux
#ifdef __linux
or
#ifdef __linux__
//FreeBSD
#ifdef __FreeBSD__
//NetBSD
#ifdef __NetBSD__
//Windows
//32bit
#ifdef _WIN32(或者WIN32)
//64bit
#ifdef _WIN64
//GUI App
#ifdef _WINDOWS
//CUI App
#ifdef _CONSOLE
//Windows的Ver … WINVER
//※ PC机Windows(95/98/Me/NT/2000/XP/Vista)和Windows CE都定义了
§ #if (WINVER >= 0x030a) // Windows 3.1以上
§ #if (WINVER >= 0x0400) // Windows 95/NT4.0以上
§ #if (WINVER >= 0x0410) // Windows 98以上
§ #if (WINVER >= 0x0500) // Windows Me/2000以上
§ #if (WINVER >= 0x0501) // Windows XP以上
§ #if (WINVER >= 0x0600) // Windows Vista以上
// Windows 95/98/Me的Ver … _WIN32_WINDOWS
§ MFC App、PC机上(Windows CE没有定义)
#ifdef _WIN32_WINDOWS
§ #if (_WIN32_WINDOWS >= 0x0400) // Windows 95以上
§ #if (_WIN32_WINDOWS >= 0x0410) // Windows 98以上
§ #if (_WIN32_WINDOWS >= 0x0500) // Windows Me以上
Windows NT的Ver … _WIN32_WINNT
§ #if (_WIN32_WINNT >= 0x0500) // Windows 2000以上
§ #if (_WIN32_WINNT >= 0x0501) // Windows XP以上
§ #if (_WIN32_WINNT >= 0x0600) // Windows Vista以上
Windows CE(PocketPC)
#ifdef _WIN32_WCE
Windows CE … WINCEOSVER
Windows CE
WCE_IF
Internet Explorer的Ver … _WIN32_IE
Cygwin
Cygwin
#ifdef __CYGWIN__
32bit版Cygwin(现在好像还没有64bit版)
#ifdef __CYGWIN32__
MinGW(-mno-cygwin指定)
#ifdef __MINGW32__