multiple definition of ‘xxx’ first defined here错误记录
自整理,以下为主要参考文章:
- C++ “multiple definition of … first defined here”-阿里云开发者社区 (aliyun.com)
- 为什么用const可以解决全局变量重定义_c语言 const 重复_jianminfly的博客-CSDN博客
- const在C语言和C++中的区别_Mr.LeoLu的博客-CSDN博客
- multiple definition重定义编译错误 & 全局变量的定义_January2021的博客-CSDN博客
- C++ multiple definition 总结 - 简书 (jianshu.com)
multiple definition case1
在C++中有时候需要在不同文件中使用同一个变量,对于这类变量如果处理不当,很容易出现“multiple definition”的错误。
其根本原因是在同一个头文件里写了变量或函数声明和定义,该文件被多个文件包含,造成变量或函数的重定义。这是因为语法规定“一个变量可以多次声明但只能定义一次。
注:一个程序运行的全过程
- 预处理将伪指令(宏定义、条件编译、和引用头文件)和特殊符号进行处理
- 预处理程序将include头文件的内容包含进源文件,这个过程完成后,头文件就没用了
- 编译过程通过词法分析、语法分析等步骤生成汇编代码的过程,过程中还会进行优化
- 汇编过程将汇编代码翻译为目标机器指令的过程(.o文件,至少包含代码段和数据段)
- 链接程序将所有需要用到的目标代码(变量函数或其他库文件等)装配到一个整体中(可分为静态链接和动态链接)
例如,定义了如下3个文件:global.h, global.cpp, test.cpp
//global.h
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
int b = 2;
void func();
#endif
//global.cpp
#include <iostream>
#include "global.h"
using namespace std;
void func() {
cout << "func is practiced" << endl;
}
//test.cpp
#include <iostream>
#include "global.h"
using namespace std;
void main() {
cout<<"hello world"<<endl;
}
执行编译命令:g++ -o test global.cpp test.cpp
,提示错误为/tmp/ccc7OcsO.o:(.bss+0x0): multiple definition of b
出错分析:a.cpp和b.cpp先分别被编译为.o格式的目标文件,两个目标文件再被链接器链接起来,这当中a.cpp和b.cpp分别进行了一次include,相当于global.h中的代码重复出现了一次,如果a是const类型,被重新多定义几次也没事;但是b只是普通变量,重复定义显然不行。
给出解决方案
有篇文章中给出了以下两种解决方案:
- 一个解决办法是把b定义为
const int
类型。或者,定义成static int
类型也行 - 还有一种解决方案,就是把global.h变为h.cpp文件,global.cpp和test.cpp中不再include它,但是编译的时候把h.cpp也编译进去,就可以了。
立即对程序进行修改,
//global.h
const int b = 2;//方案1
static int b = 2;//方案2
修改之后g++编译顺利通过,
将int b定义添加修饰static与const能通过的原因:
-
static:把global.h里面的头文件的全局变量都加上static编译便可通过,可是却会不经意出现其他问题,
static只是把变量的生存周期延长,同时也把该变量限定于当前的文件。而之所以能用于main.cpp中,是因为在编译的时候复制了一个变量名相同的变量给main.cpp而已。那么main.cpp里面的“全局变量”的改变,并不能改变原来head.h里面的全局变量的值。
-
const:当以编译时初始化的方式定义一个const对象时,就如对bufSize的定义一样const int bufSize = 512输入缓冲区大小编译器将在编译过程中把用到该变量的地方都替换成对应的值。
也就是说,编译器会找到代码中所有用到bufsize的地方,然后用512替换,为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。
结论:const解决了重定义问题的原因是其相当于在多个obj中定义了独立的变量。
-
那么当全局变量占用的静态存储区空间较大时,每个文件都定义独立的变量会让静态存储区有大量冗余内存,要怎么解决这个问题呢,C++ primer给出了解决方法使用extern关键字。
注:自定义的头文件不一定都要使用双引号进行引用,可以使用尖括号,但在编译时通过include指定路径,就不会出现问题。
解决方案出现问题
参考文章中,使用的是g++编译的c++代码,
而我使用gcc编译器编译C代码,C++解决方案在C程序中并不完全是通用的,只有static
关键字没有报错,而const
关键字依然报错,
以下是我的C代码:
//global.h
#ifndef _GLOBAL_H
#define _GLOBAL_H
int b = 1;
void func();
#endif
//global.c
#include <stdio.h>
#include "global.h"
void func() {
printf("func is practiced\n");
}
//test.c
#include <stdio.h>
#include "global.h"
int main() {
printf("helloworld\n");
return 0;
}
改为const int b = 1;
依旧报出了multiple definition的错误,这就引发了我的疑问,C与C++中的const关键字之间到底有什么区别?
为什么在C程序中使用static修饰全局变量才不会导致变量重定义的错误,而const依旧会出错。
C与C++中const的区别?
const修饰变量一定不能被修改吗?即使被const修饰其实依然是可以通过通过指针的方式对变量进行修改的。const修饰的变量,不说是“常量”,而说是“只读变量”!
#include<stdio.h>
int main() {
const int a = 10; printf("%d\n",a);
int *p = (int *)&a; *p = 20;//利用指针来修改const变量的值
printf("%d\n",a);
return 0;
}
C与C++中const的区别?相同的代码,用C语言和C++编译器编译运行后得到的结果却不相同,参考文章:
在C++中编译器不会分配存储空间,在C语言中就会分配存储空间,所以在C编译器中可以通过指针来改变用const修饰的变量的值。
在C++中const修饰的变量,在编译的时候如果前面有 extern 和取地址符 & 时,会为变量分配存储空间是为了兼容C语言,但是在C++中,用const修饰的变量就真的无法修改它的值,可以说是常亮。但是在C语言中,const修饰的变量本质上还是变量而不是常量。
在C语言中的const:
- const修饰的变量是只读,本质上还是一个变量,不是真正的常量
- const修饰的局部变量在栈上分配存储空间
- const修饰的全局变量在只读存储区中分配存储空间
- const在只在编译期有用,在运行的时候没有用
在C++语言中的const:
- const修饰的变量是常量放在符号表中,在编译的过程中,如果发现常量,则直接以符号表中的值进行替换。
- 在编译的过程中,如果发现以下情况,则为常量分配存储空间:
- 对const常量使用了 extern
- 对const常量使用了取地址符&
- 在这两种情况下,虽然会为常量分配存储空间,但是却不会使用该存储空间中的值
最终的解决方案
不论是使用const、还是使用static来解决multiple definition问题都是不好的,参考January2021的文章给出了最终的详细解决方案,
-
内部链接:如果一个名称对于它的编译单元来说是局部的,并且在链接的时候不会与其它编译单元中同样的名称相冲突,则这个名称具有内部链接。
-
外部链接:如果一个名称在链接时可以和其他编译单元交互,那么这个名称就具有外部链接。
最终的解决方案:将变量或函数声明和定义分开,全局变量声明extern,全局常量声明extern const
extern修饰的变量具有外部链接属性,可以实现全局变量的属性,与const结合就可以实现全局和只读变量的目的,
但需要说明的是,变量必须在头文件中给出声明而不是定义,然后在与头文件对应的源文件中给出定义(也可以在任意引用该头文件的源文件中给出定义,但不推荐)
multiple definition case2
实际情况中发现,除了提到的case1会引发multiple definition错误,还有另一种情况case2也会引发这种错误,C程序代码如下:
//global.h
#ifndef _GLOBAL_H
#define _GLOBAL_H
char ans[512] = {0};
void func();
#endif
//global.c
#include <stdio.h>
#include "global.h"
void func() {
printf("func is practiced\n");
}
//test.c
#include <stdio.h>
#include "global.h"
int main() {
printf("helloworld\n");
return 0;
}
执行编译命令:gcc -o test global.c test.c
,提示错误为/tmp/ccc7OcsO.o:(.bss+0x0): multiple definition of ans
如果将global.h文件中的char ans[512] = {0};
改为char ans[512];
就不会引发multiple definition of ans的错误,
- 这其实还是和case1本质一样的问题,只不过将对于一个整型的定义修改为了数组的定义,
- 其核心问题还是一个变量可以多次声明,但只能定义一次。
- 报错记录到此结束,
总结:涉及问题包括
- 几种处理multiple definition错误的方式
- C与C++中const的区别
- 一个程序运行的全过程