概述:在上一篇我们知道 通过 #ifndef....#defin....#endif , 这个解决头文件重复包含的问题
C++:重定义:class类型重定义_hongwen_yul的博客-CSDN博客
避免头文件的重复包含可以有效的避免变量的重复定义,其实不光变量可以避免重复定义,也可以避免函数和类、结构体的重复定义。但是避免头文件的重复包含是否一定可以避免 变量、函数、类、结构体的重复定义了 ?答案当然是否定的,下面我们通过GCC工具来探究 “C++ 预处理---》编译----》链接” 这几个过程。
1:GCC下载,教程很详细,自己看下就可以了
Windows下GCC安装和使用_丸子爱学习!的博客-CSDN博客_windows gcc
gcc 常见的命令,参照下面这篇文章
C++:GCC编译:GCC编译C++程序分步流程_hongwen_yul的博客-CSDN博客
2: #ifndef....#defin....#endif 解决预编译阶段头文件重复包含
2.1 :头文件在没有添加 #ifndef....#defin....#endif情况下
a.h
int A = 2;
b.h
#include<string>
#include"a.h"
void fb();
b.cpp
#include"b.h"
void fb() {
printf("%d", A+1);
}
c.h
#pragma once
#include<string>
#include"a.h"
void fc();
c.cpp
#include"c.h"
void fc() {
printf("%d", A + 2);
}
main.cpp
#include<string>
#include"b.h"
#include"c.h"
#include<string>
int main() {
fb();
fc();
return 0;
}
// a.h b.h c.h 这三个头文件没有添加 #ifndef....#defin....#endif 情况下
// 通过 gcc -E main.cpp 预编译结果
# 2 "main.cpp" 2
# 1 "b.h" 1
# 1 "a.h" 1
# 1 "a.h"
int A = 2; // 第一次定义变量 A
# 3 "b.h" 2
void fb();
# 3 "main.cpp" 2
# 1 "c.h" 1
# 1 "a.h" 1
int A = 2; // 第二次定义变量 A
# 3 "c.h" 2
void fc();
# 4 "main.cpp" 2
int main() {
fb();
fc();
return 0;
}
// 很明显 预编译之后的结果:main.cpp 会定义两次 变量 A ,所以通过 gcc -C main.cpp 就会出现变量重复定义问题
$ gcc -C main.cpp
In file included from c.h:2:0,
from main.cpp:3:
a.h:1:5: error: redefinition of 'int A'
int A = 2;
^
In file included from b.h:2:0,
from main.cpp:2:
a.h:1:5: note: 'int A' previously defined here
int A = 2;
^
2.2 头文件添加 #ifndef....#define....#endif 或者 #pragma once
可以看到main.cpp 编译后没有任何影响,从而避免了重复包含的问题
a.h
#pragma once
int A = 2;
===================================================================================
b.h
#pragma once
#include<string>
#include"a.h"
void fb();
b.cpp
#include"b.h"
void fb() {
printf("%d", A+1);
}
==========================================================================================
c.h
#pragma once
#include<string>
#include"a.h"
void fc();
c.cpp
#include"c.h"
void fc() {
printf("%d", A + 2);
}
=========================================================================================
main.cpp
#include<string>
#include"b.h"
#include"c.h"
#include<string>
int main() {
fb();
fc();
return 0;
}
// gcc -E main.cpp 预编译结果
# 2 "main.cpp" 2
# 1 "b.h" 1
# 1 "a.h" 1
# 2 "a.h"
int A = 2;
# 4 "b.h" 2
void fb();
# 3 "main.cpp" 2
# 1 "c.h" 1
void fc();
# 4 "main.cpp" 2
int main() {
fb();
fc();
return 0;
}
// gcc -C -S main.cpp 编译的结果
.file "main.cpp"
.section .rdata,"dr"
__ZStL19piecewise_construct:
.space 1
.globl _A
.data
.align 4
_A:
.long 2
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB935:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
call ___main
call __Z2fbv
call __Z2fcv
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE935:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def __Z2fbv; .scl 2; .type 32; .endef
.def __Z2fcv; .scl 2; .type 32; .endef
3:如何解决链接的时候重复包含问题
- 我们知道c/c++编译的基本单元是.c或.cpp文件,各个基本单元的编译是相互独立的,#ifndef等条件编译只能保证在一个基本单元(单独的.c或.cpp文件)中头文件不会被重复编译,
- 但是无法保证两个或者更多基本单元中相同的头文件不会被重复编译
a.h
#pragma once
int A = 2;
==================================================================================
b.h
#pragma once
#include<string>
#include"a.h"
void fb();
b.cpp
#include"b.h"
void fb() {
printf("%d", A+1);
}
// 预编译 b.cpp
gcc -E b.cpp
# 3 "b.h" 2
# 1 "a.h" 1
# 2 "a.h"
int A = 2;
# 4 "b.h" 2
void fb();
# 2 "b.cpp" 2
void fb() {
printf("%d", A+1);
}
// 编译 gcc -C -S b.cpp -o b.txt
.file "b.cpp"
.section .rdata,"dr"
__ZStL19piecewise_construct:
.space 1
.globl _A
.data
.align 4
_A:
.long 2
.section .rdata,"dr"
LC0:
.ascii "%d\0"
.text
.globl __Z2fbv
.def __Z2fbv; .scl 2; .type 32; .endef
__Z2fbv:
LFB935:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl _A, %eax
addl $1, %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE935:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def _printf; .scl 2; .type 32; .endef
====================================================================================
c.h
#pragma once
#include<string>
#include"a.h"
void fc();
c.cpp
#include"c.h"
void fc() {
printf("%d", A + 2);
}
// 预编译 gcc -E c.cpp
# 3 "c.h" 2
# 1 "a.h" 1
# 2 "a.h"
int A = 2;
# 4 "c.h" 2
void fc();
# 2 "c.cpp" 2
void fc() {
printf("%d", A + 2);
}
// 编译 gcc -C -S c.cpp -o c.txt
.file "c.cpp"
.section .rdata,"dr"
__ZStL19piecewise_construct:
.space 1
.globl _A
.data
.align 4
_A:
.long 2
.section .rdata,"dr"
LC0:
.ascii "%d\0"
.text
.globl __Z2fcv
.def __Z2fcv; .scl 2; .type 32; .endef
__Z2fcv:
LFB935:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl _A, %eax
addl $2, %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE935:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def _printf; .scl 2; .type 32; .endef
================================================================================
main.cpp
#include<string>
#include"b.h"
#include"c.h"
#include<string>
int main() {
fb();
fc();
return 0;
}
// 预编译 gcc -E main.cpp
# 2 "main.cpp" 2
# 1 "b.h" 1
# 1 "a.h" 1
# 2 "a.h"
int A = 2;
# 4 "b.h" 2
void fb();
# 3 "main.cpp" 2
# 1 "c.h" 1
void fc();
# 4 "main.cpp" 2
int main() {
fb();
fc();
return 0;
}
// 编译 gcc -C -S main.cpp -o main.txt
.file "main.cpp"
.section .rdata,"dr"
__ZStL19piecewise_construct:
.space 1
.globl _A
.data
.align 4
_A:
.long 2
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB935:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
call ___main
call __Z2fbv
call __Z2fcv
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE935:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def __Z2fbv; .scl 2; .type 32; .endef
.def __Z2fcv; .scl 2; .type 32; .endef
=====================================================================================
从预编译和编译的过程我们看出,b.cpp / c.cpp / main.cpp 这三个文件的预编译和编译过程均没有问题
那么我们看一下,这个程序最后一步:链接过程
从预编译和编译的过程我们看出,b.cpp / c.cpp / main.cpp 这三个文件的预编译和编译过程均没有问题
那么我们看一下,这个程序最后一步:链接过程
1: 然后分别编译gcc -c b.cpp -o b.o和gcc -c main.cpp -o main.o
2: 然后链接 b.o 和main.o 文件 (gcc b.o main.o -o main,)就会出错,为什么了
- gcc -c b.cpp -o b.o ---->b.cpp文件被编译成b.o文件,在这个过程中,预处理阶段编译器还是会打开a.h文件,定义#pragma once 并将a.h包含进b.cpp中。
- gcc -c c.cpp -o c.o---->c.cpp文件被编译成c.o文件,在这个过程中,请注意预处理阶段,编译器依旧打开a.h文件,此时的#pragma once是否已被定义呢?前面提到不相关的.cpp文件之间的编译是相互独立的,自然,b.cpp的编译不会影响c.cpp的编译过程,
- 所以c.cpp中的#pragma once不会受前面b.cpp中#pragma once的影响,也就是c.cpp的#pragma once是未定义的!!于是编译器再次干起了相同的活,定义#pragma once,包含将a.h包含进c.cpp中。
到此,我们有了b.o和c.o,编译main.cpp后有了main.o,再将它们链接起来生成main时出现问题了:
- 编译器在编译.c或.cpp文件时,有个很重要的步骤,就是给这些文件中含有的已经定义了的变量分配内存空间,
- 在a.h中A就是已经定义的变量,由于b.cpp和c.cpp独立,所以A相当于定义了两次,分配了两个不同的内存空间。
- 在main.o链接b.o和c.o的时候,由于main函数调用了fb和fc函数,这两个函数又调用了A这个变量,对于main函数来说,A变量应该是唯一的,应该有唯一的内存空间,但是fb和fc中的A被分配了不同的内存,内存地址也就不同,main函数无法判断那个才是A的地址,产生了二义性,所以程序会出错。
4: 如何避免重复定义
讲了这么多,那么到底怎么样才能避免重复定义呢?
其实避免重复定义关键是要避免重复编译,防止头文件重复包含是有效避免重复编译的方法,但是最好的方法还是记住那句话:头文件尽量只有声明,不要有定义。这么做不仅仅可以减弱文件间的编译依存关系,减少编译带来的时间性能消耗,更重要的是可以防止重复定义现象的发生,防止程序崩溃。