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

news2025/7/18 8:18:45

概述:在上一篇我们知道 通过 #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: 如何避免重复定义

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]java毕业设计基于web旅游网站的设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

使用Docker开发GO应用程序

根据Stack Overflow的2022开发者调查&#xff0c;Go&#xff08;或Golang&#xff09;是最受欢迎和最受欢迎的编程语言之一。由于与许多其他语言相比&#xff0c;Go的二进制大小更小&#xff0c;开发人员经常使用Go进行容器化应用程序开发。 Mohammad Quanit在他的社区全能课程…

小程序vant-tabbar使用示例,及报错处理

小程序vant-tabbar使用示例&#xff0c;及报错处理1. 配置信息2. 添加 tabBar 代码文件3. 编写 tabBar 代码custom-tab-bar/index.tscustom-tab-bar/index.jsoncustom-tab-bar/index.wxml使小程序使用vant-tabbar组件时&#xff0c;遇到以下报错&#xff1a;Couldn’t found th…

Java基于springboot+vue的儿童玩具销售购物网站 多商家

爱玩儿是所有孩子的天性。尤其是在婴幼儿阶段。选择一个好的玩具&#xff0c;不仅能够让孩子玩儿的开心&#xff0c;而且有助于孩子智力的开发。很多家长在选择玩具的时候&#xff0c;不知道选择什么样的玩具。且当前玩具市场的玩具鱼目混杂&#xff0c;种类繁多&#xff0c;而…

SAR信号处理基础1——线性调频信号

关键字&#xff1a;线性调频信号&#xff0c;LFM信号&#xff0c;chirp信号&#xff0c;驻定相位原理&#xff08;POSP&#xff09;&#xff0c;泰勒展开&#xff0c;Taylor展开&#xff0c;脉冲压缩&#xff0c;匹配滤波&#xff0c;sinc&#xff0c;分辨率&#xff0c;峰值旁…

QProgressDialog.close()失败,进度条关闭感觉失败了,无法彻底关闭

开发环境&#xff1a;我是在deepin&#xff08;深度&#xff09;系统下开发的&#xff0c;在我本机上&#xff0c;一点问题也没有&#xff0c;但是我移植到了ubantu的机子上&#xff0c;就偶尔出现出个问题&#xff0c;出现了一个模态框&#xff0c;需要重启软件才能关闭。 问题…

Vue的computed和watch的区别是什么?

一、computed介绍 computed 用来监控自己定义的变量&#xff0c;该变量在 data 内没有声明&#xff0c;直接在 computed 里面定义&#xff0c;页面上可直接使用。 //基础使用 {{msg}} <input v-model"name" /> //计算属性 computed:{msg:function(){return …

【MySQL】MySQL日志系统以及InnoDB背后的技术(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

Java基于springboot+vue的个人博客网站 前后端分离

随着现在网络的快速发展&#xff0c;网上管理系统也逐渐快速发展起来&#xff0c;网上管理模式很快融入到了许多网站的之中&#xff0c;随之就产生了“博客网站”&#xff0c;这样就让博客网站更加方便简单。 对于本博客网站的设计来说&#xff0c;系统开发主要是采用java语言技…

2022国产8K摄像机介绍

摄像机是一种把光学图像信号转变为电信号&#xff0c;以便于存储或者传输的设备。当我们拍摄一个物体时&#xff0c;此物体上反射的光被摄像机镜头收集&#xff0c;使其聚焦在摄像器件的受光面&#xff08;例如摄像管的靶面&#xff09;上&#xff0c;再通过摄像器件把光转变为…

N-HiTS: Neural Hierarchical Interpolation for Time Series Forecasting

N-HiTS: Neural Hierarchical Interpolation for Time Series Forecasting 神经预测的最新进展加速了大规模预测系统性能的提高。然而,长期预测仍然是一项非常困难的任务。影响这项任务的两个常见挑战是预测的波动性和它们的计算复杂性。本文提出N-HiTS,一种通过结合新的分层…

不同字符编码对比

目录 1. ASCII码 2. Unicode 3. GBK编码 1. ASCII码 ASCII码使用一个字节编码&#xff0c;但只适用于英文&#xff1b; 2. Unicode Unicode定义了字符集&#xff0c;有 17 个 code plane&#xff0c;总共规划了 1,114,112 个 code point。而这些字符可以使用UTF-8、UTF-1…

Windows无法访问指定设备、路径或文件怎么办?

如何解决Windows 无法访问指定的设备、路径或文件错误&#xff1f; 1.修改安全中心的设置 如果在安装程序的过程中&#xff0c;遇到该错误&#xff0c;可以进入到【Windows安全中心】进行设置修改。 第一步&#xff1a;点击左下角的开始按钮&#xff0c;然后依次点击【设置】…

直接安装WSL2及安装Ubuntu到F盘

1. 勾选这三项&#xff0c;重启 2. 以管理员方式运行powersell wsl --updatewsl --shutdownwsl --set-default-version 2wsl --status3. 解压缩ubuntu 解压缩Ubuntu_1804.2019.522.0_x64.appx到F盘 4. 安装ubuntu 双击ubuntu1804.exe安装 5. 运行 双击ubuntu1804.exe …

AI人工智能实践技术系统性教学方案

郁磊老师【副教授】 &#xff1a;长期从事Python、Matlab机器学习及深度学习等研究工作&#xff0c;具备良好的数学及信号处理基础&#xff0c;熟悉如神经网络、支持向量机、决策树、随机森林等&#xff0c;以及群优 化算法&#xff0c;如遗传算法、蚁群算法、蝙蝠算法等&#…

React源码解读之更新的创建

React 的鲜活生命起源于 ReactDOM.render &#xff0c;这个过程会为它的一生储备好很多必需品&#xff0c;我们顺着这个线索&#xff0c;一探婴儿般 React 应用诞生之初的悦然。 更新创建的操作我们总结为以下两种场景 ReactDOM.rendersetStateforceUpdate ReactDom.render …

k8s-dynamic-pvc

安装 storage class: external-storage/nfs-client/deploy at master kubernetes-retired/external-storage GitHub 下载文件并安装: class.yaml deployment.yaml rbac.yaml 其中修改: 安装 测试: [rootmaster test-dir]# cat nginx-1.yaml apiVersion: v1 kind: Pers…

【Pytorch with fastai】第 4 章 :底层训练数字分类器

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

一切为了喵喵 | 攻防世界 x Nepnep x CATCTF邀你一战!

各位极客请注意❗ 各位极客请注意❗❗ 为了坚决捍卫每只小猫咪吃饱饭的权利 为了彻底贯彻“可爱就是王道”的正义 攻防世界 x Nepnep x CATCTF 邀你一战&#xff01; 此战若成&#xff0c;8333只流浪喵星人将能饱餐一顿&#xff01; 没有一只修猫咪饿肚子的明天在等着我们…

【Python】初始Python

文章目录一. Python背景知识1. Python的起源2. Python的作用3. Python的优缺点4. Python的前景二. 搭建Python环境1. 安装Python2. 安装 PyCharm一. Python背景知识 1. Python的起源 Python祖师爷吉多 范罗苏姆&#xff08;Guido van Rossum&#xff09;是一个荷兰程序员&am…