C/C++中头文件重复包含和变量重复定义

在头文件重复包含和变量重复定义的错误提示中多半会包含这样一个单词----redefinition

一、如何避免头文件重复包含

1. 为何要避免头文件重复包含的原因?

  • 在编译c或c++程序时候,编译器首先要对程序进行预处理,预处理其中一项工作便是将你源程序中#include的头文件完整的展开,如果你有意或无意的多次包含相同的头文件,会导致编译器在后面的编译步骤多次编译该头文件,工程代码量小还好,工程量一大会使整个项目编译速度变的缓慢,后期的维护修改变得困难
  • 头文件重复包含带来的最大坏处是会使程序在编译链接的时候崩溃,这是我们无法容忍的

2. 解决方法
先来看个会出现重定义错误的例子:

//a.h  
#include<stdio.h>  
int A=1;  
  
  
//b.h  
#include "a.h"  
void f(){printf("%d",A);}  
  
//main.c  
#include<stdio.h>  
#include"a.h"  
#include"b.h"  
void main(){f();} 

方法1:条件编译

#ifndef _XXX
#define _XXX
...
#endif

具体用法如下:

#include<stdio.h>  
#include "a.h"  
#include "a.h"  
  
void main()  
{  
   printf("%d",A);  
}  

//a.h  
  
#include<stdio.h>  
#ifndef _A_H  
#define _A_H  
  
int A = 1;  
  
#endif;

条件编译:

编译main.c时,预处理阶段遇到①,编译器打开a.h,发现_A_H未定义,于是将 #define到#endif之间的内容包含进main.c;当遇到②时,编译器再次打开a.h,发现_A_H已经定义,于是直接关闭a.h,a.h没有再次包含进main.c,从而避免了重复包含。

方法2:

//a.h  
  
#pragma once  
#include<stdio.h>  
int A = 1;  

预处理阶段遇到①时,打开a.h,将#pragma once后面的内容包含进main.c中,关闭a.h。遇到②时,编译器直接跳过该语句,执行后面的语句,从而避免重复包含。

二、变量重复定义

讲完了文件的重复包含,让我们来思考一个问题:如前所说,避免头文件的重复包含可以有效地避免变量的重复定义,其实不光是变量的重复定义,也可以避免函数和类、结构体的重复定义。但是
避免头文件的重复包含是否一定可以避免变量、函数、类、结构体的重复定义?

答案当然是否!

让我们再看上面的例子:

//a.h  
  
#include<stdio.h>  
#ifndef _A_H  
#define _A_H  
  
int A = 1;  
  
#endif;  
//b.h  
  
#include<stdio.h>  
#include "a.h"  
void f();  
  
//b.c  
  
#include"b.h"  
void f()  
{  
   printf("%d",A+1);  
}  
  
//c.h  
  
#include<stdio.h>  
#include "a.h"  
void fc();  
  
//c.c  
  
#include"c.h"  
void fc()  
{  
   printf("%d",A+2);  
}  
//main.c  
  
#include<stdio.h>  
#include "b.h"  
#include "c.h"  
void main()  
{  
    fb();  
    fc();  
}
为什么会出错呢?按照条件编译,a.h并没有重复包含,可是还是提示变量A重复定义了。
在这里我们要注意一点,变量,函数,类,结构体的重复定义 不仅会发生在源程序编译的时候,在目标程序链接的时候同样也有可能发生 。我们知道c/c++编译的基本单元是.c或.cpp文件,各个基本单元的编译是相互独立的,#ifndef等条件编译只能保证在一个基本单元(单独的.c或.cpp文件)中头文件不会被重复编译,但是无法保证两个或者更多基本单元中相同的头文件不会被重复编译,不理解?没关系,还是拿刚才的例子讲:
gcc -c b.c -o b.o :b.c文件被编译成b.o文件,在这个过程中,预处理阶段编译器还是会打开a.h文件,定义_A_H并将a.h包含进b.c中。
gcc -c c.c -o c.o:c.c文件被编译成c.o文件,在这个过程中,请注意预处理阶段,编译器依旧打开a.h文件,此时的_A_H是否已被定义呢?前面提到不相关的.c文件之间的编译是相互独立的,自然,b.c的编译不会影响c.c的编译过程,所以 c.c中的_A_H不会受前面b.c中_A_H的影响,也就是c.c的_A_H是未定义的 !!于是编译器再次干起了相同的活,定义_A_H,包含_A_H。
到此,我们有了b.o和c.o,编译main.c后有了main.o,再将它们链接起来生成main时出现问题了:
编译器在编译.c或.cpp文件时,有个很重要的步骤,就是给这些文件中含有的 已经定义了的变量分配内存空间 ,在a.h中A就是已经定义的变量,由于b.c和c.c独立,所以A相当于定义了两次,分配了两个不同的内存空间。在main.o链接b.o和c.o的时候,由于main函数调用了fb和fc函数,这两个函数又调用了A这个变量,对于main函数来说, A变量应该是唯一的,应该有唯一的内存空间 ,但是fb和fc中的A被分配了不同的内存,内存地址也就不同,main函数无法判断那个才是A的地址, 产生了二义性 ,所以程序会出错。

讲了这么多,那么到底怎么样才能避免重复定义呢?
其实避免重复定义关键是要 避免重复编译 ,防止头文件重复包含是有效避免重复编译的方法,但是最好的方法还是记住那句话: 头文件尽量只有声明,不要有定义 。这么做不仅仅可以减弱文件间的编译依存关系,减少编译带来的时间性能消耗,更重要的是可以防止重复定义现象的发生,防止程序崩溃。

三、参考文献




猜你喜欢

转载自blog.csdn.net/qq_34809033/article/details/80652116