C++:重定义:符号重定义:变量重定义

概述:在上一篇我们知道 通过 #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:如何解决链接的时候重复包含问题

  1. 我们知道c/c++编译的基本单元是.c或.cpp文件,各个基本单元的编译是相互独立的,#ifndef等条件编译只能保证在一个基本单元(单独的.c或.cpp文件)中头文件不会被重复编译,
  2. 但是无法保证两个或者更多基本单元中相同的头文件不会被重复编译
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,)就会出错,为什么了

  1.  gcc -c b.cpp -o b.o  ---->b.cpp文件被编译成b.o文件,在这个过程中,预处理阶段编译器还是会打开a.h文件,定义#pragma once 并将a.h包含进b.cpp中。
  2. gcc -c c.cpp -o c.o---->c.cpp文件被编译成c.o文件,在这个过程中,请注意预处理阶段,编译器依旧打开a.h文件,此时的#pragma once是否已被定义呢?前面提到不相关的.cpp文件之间的编译是相互独立的,自然,b.cpp的编译不会影响c.cpp的编译过程,
  3. 所以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: 如何避免重复定义

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值