1、引言
符号重定义(Symbol Redefinition)指的是在同一个作用域内多次定义同名标识符(包括变量、函数、类等)。符号重定义错误通常包括预处理期符号重定义,编译期符号重定义,链接期符号重定义,以及运行期符号重定义。
2、符号重定义场景
2.1、同名宏
如果在多个地方重复定义同一个宏,则会在预处理阶段导致符号重定义错误。
//file1.h
#define MAX 100
//file2.h
#define MAX 50
//main.c
#include "file1.h"
#include "file2.h"
int main() {
return 0;
}
在这个例子中,当编译器编译 main.c 时,会发现 MAX 宏在两个头文件中都被定义了,从而导致符号重定义错误。
2.2、名称冲突
同一作用域或不同作用域内出现了相同的符号名定义,会造成编译期错误。下面是一个C++示例代码,演示了编译期符号重定义错误:
#include <iostream>
int value = 3;
int value = 5;// 错误:value重定义
int main()
{
std::cout << value << std::endl;
return 0;
}
在上述代码中,我们在同一作用域中定义了两个名为value的变量,这会导致编译器无法确定应该取哪个变量的值,所以会在编译期间报错,提示符号重定义的问题。
2.3、头文件多重包含
头文件多重包含的问题是这样产生的:当一个源文件包含多个头文件时,某个头文件可能会包含一个已经包含过的头文件,导致同一个函数或变量的定义被重复包含。下面是一个示例,展示了多重包含相同的头文件引起重定义的错误:
//add.h文件
int add(int a,int b){
return a+b;
}
//printAdd.h文件
#include<iostream>
#include"add.h>
void printAdd(int a,int b){
std::cout<<add(a,b)<<std::endl;
}
//main.cpp文件
#include"printAdd.h"
#include"add.h" //符号重定义
int main(){
printAdd(1,2);
}
上述代码中,add.h文件在main.cpp内被2次包含,这就导致add()函数发生两次定义,所以会在编译期间报错,提示符号重定义的问题。
2.4、链接错误
链接重定义错误是一种常见的链接错误,通常是由于多个编译单元中出现了相同的符号定义而导致的。这种错误会在链接时被检测到,表示无法解析符号引用,因为有多个定义存在。
//a.h
int getValue();
//a.cpp
int getValue(){return 0;};
//b.h
int helgetValuelo();
//b.cpp
int getValue(){return 1;}
我们将a.cpp和 b.cpp 分别编译为目标文件a.o和b.o,并尝试将其链接到一起,会导致符号重定义错误。
2.5、运行期符号重定义
符号重定义错误通常是在编译期间被检测到的,而不是在运行时。然而,有些情况下可能会在运行时发生符号重定义错误。例如,在动态链接库或共享对象中,函数或变量可以在运行时加载和卸载。如果在两个动态链接库中定义了相同名称的函数或变量,它们可能会导致符号重定义错误。
3、符号重定义机制
c++ 采用单文件编译,在编译期间将这些文件单独编译成目标文件,然后通过链接器将它们组合成一个可执行文件。编译器在把单个源码文件编译成目标文件过程中,会检测源码中的同名符号,从而发现预处理期符号重定义,编译期符号重定义的错误。目标文件格式如下:
目标文件头ELF Header内存放有符号信息,把多个目标文件链接到一起的时候,若发现文件头内有同名符号,就会报链接期符号重定义错误。接下来看一下符号表结构:
4、解决符号重定义的方法
这里列举了6种解决符号重定义问题的方法,其中static,inline,extern,const只能用于解决链接期重定义问题;
预处理指令只用于解决编译期重定义问题;
命名空间可解决预处理期和编译期重定义问题