使用现代 gcc 编译抛出错误多重定义的旧 C 代码在 Linux 上?

问题描述

我为什么要这个?

我想使用最初于 2007 年构建并根据更新日志于 2016 年更新的 C package。我认为它会编译干净。

遗憾的是,情况不再如此。

错误

运行 ./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

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

double *Data; /*the array of data*/
unsigned Size;/*the number of data*/

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

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

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

load(&Data,infile);

int load(...)

调用 common.c 函数
int load(double **data,unsigned *size,FILE *input)

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

And *size 函数获取&Size)是地址&Size中的值,所以另一个全局变量

函数然后多次写入 *data and *size ,例如,最后:

*size=i;
*data = (double *) my_realloc((void *) *data,(*size)*sizeof(double));

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

此外,评论说我实际上并没有足够的 C 知识来诊断程序,因此我应该雇用一个这样做的人。这会将允许在 Stackoverflow 中发帖的门槛提高到一个非常高的水平;在通常张贴并被视为完全可以接受的问题中并不总是达到的水平。这实际上可能是一个合理的建议,但它不会让我有机会提出有关 C 或任何其他语言的问题。如果评论的作者对此是认真的,那么可能值得在 Meta 上发帖并建议将 Stackoverflow 分成两部分,一个给专家,一个给其他人。

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

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

  • 重写软件包避免多重定义。理想情况下,我希望避免这种情况。
  • 找到一种编译方式,因为它本来是在 2007 年到 2016 年之间编译的。我认为当时它会编译得很干净。这有多个潜在问题:旧编译器是否仍适用于我的 2021 系统?这可以与现代系统中的图书馆一起使用吗?即使我成功了,生成的可执行文件是否会像作者预期的那样运行?不过,这似乎是更可取的选择。

也有可能我误解了错误或误解了某些东西。

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

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

包括 kaylum's answer below 中讨论的 -fcommon 选项。

尝试通过更改代码解决

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

double *Data; /*the array of data*/
unsigned Size;/*the number of data*/

为了

extern double *Data; /*the array of data*/
extern unsigned Size;/*the number of data*/

代码再次干净地编译。不过,我不确定我对 bahavior 实际上与作者预期的相同(并且使用旧的 gcc 版本和最近的 gcc 使用 -fcommon 实现)的确定程度。

为什么我认为这个问题对编程具有普遍意义(这属于 Stackoverflow)

然而,我猜这个问题更笼统。周围有很多旧的软件包。如果有足够的时间,它们中的大多数最终都会破裂。

软件

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

解决方法

如果源代码是用 -fcommon 构建的,则 gcc 中允许多个同类型同名全局变量定义。来自gcc manual

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

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

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

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

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