使用现代 gcc 编译旧的 C 代码(在 Linux 上)会引发错误(多重定义)?

0ra*_*nge 0 c linux compilation include multiple-definition-error

我为什么想要这个?

我想使用最初于 2007 年构建并根据变更日志于 2016 年最新更新的C 软件包。我认为它会干净地编译回来。

可悲的是,现在情况已不再如此。

错误

运行./configuremake,我得到一个Multiply defined错误:

gcc  -g -O2   -o laplaafit laplaafit.o multimin.o common.o lib/libgnu.a -lgsl -lgslcblas -lm 
/usr/bin/ld: common.o:/home/<user>/build/subbotools/subbotools-1.3.0/common.c:27: multiple definition of `Size'; laplaafit.o:/home/<user>/build/subbotools/subbotools-1.3.0/laplaafit.c:38: first defined here
/usr/bin/ld: common.o:/home/<user>/build/subbotools/subbotools-1.3.0/common.c:26: multiple definition of `Data'; laplaafit.o:/home/<user>/build/subbotools/subbotools-1.3.0/laplaafit.c:37: first defined here
Run Code Online (Sandbox Code Playgroud)

具体来说,两个文件 (laplaafit.ccommon.c) 都有声明

double *Data; /*the array of data*/
unsigned Size;/*the number of data*/
Run Code Online (Sandbox Code Playgroud)

在两个文件的代码中进一步定义了两个变量(我相信使用load(&Data,&Size,infile);which 调用函数int load(),在该函数中从文件common.c读取数组并确定其长度)。*DataSize

这就是导致错误的原因。这些变量在这两个文件中都很重要(删除任一文件都会导致'(variable)' undeclared错误)。common.h如果两个文件中都包含头文件(例如 ),则移动到头文件不会改变任何内容.c

编辑:由于评论中提出的load(&Data,&Size,infile);“远非定义”,我认为我应该更详细一些。

load(&Data,&Size,infile);
Run Code Online (Sandbox Code Playgroud)

int load(...)从调用该函数common.c

int load(double **data,unsigned *size,FILE *input)
Run Code Online (Sandbox Code Playgroud)

这里, *Data 是从地址开始的数组Data&Data是指向数组开头的指针(双指针?)的指针。 **data 是一个指向本地数组的双指针load()。如果函数&Data为此获取,data实际上引用原始全局数组,并且程序可以通过指针访问它来写入它 *data

并且 *size (函数为其获取&Size)是地址中的值&Size,因此是另一个全局变量。

然后该函数多次写入 和 *data *size 例如在最后:

*size=i;
*data = (double *) my_realloc((void *) *data,(*size)*sizeof(double));
Run Code Online (Sandbox Code Playgroud)

如果我没记错的话,这可以算作全局变量 *Data 并被Size定义。

此外,评论说我实际上不知道足够的 C 来诊断该程序,因此我应该雇用一个知道的人。这将把在 Stackoverflow 上发帖的门槛提高到一个非常高的水平;在通常发布并被视为完全可以接受的问题中并不总是能够达到这一水平。这实际上可能是一个合理的建议,但它不会给我留下任何地方来提出我可能对 C 或任何其他语言感兴趣的问题。如果评论的作者对此很认真,那么可能值得在 Meta 上发帖,并建议将 Stackoverflow 一分为二,一个供专家使用,一个供其他人使用。

如何解决问题(使代码编译)

在我看来,有两种方法可以解决这个问题:

  • 重写软件包避免多重定义。我希望避免这种情况。
  • 找到一种编译方法,因为它会在 2007 年到 2016 年之间编译。我认为那时它会编译得很干净。这有多个潜在的问题:旧的编译器是否仍然适用于我的 2021 年系统?这适用于现代系统中的图书馆吗?即使我成功了,生成的可执行文件的行为是否符合作者的预期?尽管如此,这似乎是更好的选择。

我也有可能误解错误或误解某些内容。

也有可能即使在 2007 年到 2016 年之间,我的编译器 (gcc) 也无法干净地编译它,并且作者使用了接受多个定义的不同编译器。

通过使用旧编译器行为进行编译的解决方案

包括下面 kaylum 的回答-fcommon中讨论的选项。

尝试通过修改代码解决

预期的行为显然是两个变量DataSize两个文件中引用同一变量(内存中的同一点)。因此,将变量声明为externinlaplaafit.c应该会恢复相同的行为。具体来说,交换

double *Data; /*the array of data*/
unsigned Size;/*the number of data*/
Run Code Online (Sandbox Code Playgroud)

为了

extern double *Data; /*the array of data*/
extern unsigned Size;/*the number of data*/
Run Code Online (Sandbox Code Playgroud)

代码再次编译干净。我不确定我有多确定该行为实际上与作者的意图相同(并且通过旧版本gcc和最新版本实现)。gcc-fcommon

为什么我认为这个问题是编程界普遍感兴趣的(这属于 Stackoverflow)

然而,我猜这个问题更为普遍。周围有许多旧的软件包。只要有足够的时间,它们中的大多数最终都会破裂。

软件

我的系统是Arch Linux kernel 5.11.2;C编译器:gcc 10.2.0;GNU Make 4.3。

kay*_*lum 6

如果源代码是用-fcommon. 来自海湾合作委员会手册

-fcommon 将未初始化的全局变量放置在公共块中。这允许链接器将不同编译单元中同一变量的所有暂定定义解析为同一对象,或解析为非暂定定义。此行为与 C++ 不一致,并且在许多目标上意味着全局变量引用的速度和代码大小损失。它主要用于使遗留代码能够无错误地链接。

10 之前的 gcc 的默认值曾经是,-fcommon但在 gcc 10 中已更改为。-fno-common来自gcc 10 发行说明

GCC 现在默认为 -fno-common。因此,全局变量访问在各种目标上更加高效。在 C 中,具有多个暂定定义的全局变量现在会导致链接器错误。使用 -fcommon 时,此类定义会在链接期间以静默方式合并。

这解释了为什么在使用 gcc 10 的环境中构建失败,但能够使用较旧的 gcc 版本进行构建。您的选择是添加-fcommon到构建中或使用 10 之前的 gcc 版本。

或者正如 @JohnBollinger 所指出的,另一种选择是修复代码以删除这些多个定义并使代码严格符合 C 标准。