用现代 gcc 编译抛出错误(乘法定义)的旧 C 代码(在 Linux 上)? [关闭]

Posted

技术标签:

【中文标题】用现代 gcc 编译抛出错误(乘法定义)的旧 C 代码(在 Linux 上)? [关闭]【英文标题】:Compile old C code (on Linux) that throws errors (Multiply defined) with modern gcc? [closed] 【发布时间】:2021-03-07 03:08:30 【问题描述】:

我为什么要这个?

根据变更日志,我想使用最初于 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(&amp;Data,&amp;Size,infile);common.c 中调用函数int load(),它从文件中读取数组*Data 并确定其长度@ 987654335@)。

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

编辑:由于在 cmets 中提出 load(&amp;Data,&amp;Size,infile);“远不是一个定义”,我想我应该更详细一点。

load(&Data,&Size,infile);

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

int load(double **data,unsigned *size,FILE *input)

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

*size(函数获得&amp;Size)是地址&amp;Size中的值,所以是另一个全局变量。

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

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

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

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

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

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

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

我仍然可能误解了错误或误解了某些内容。

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

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

包括-fcommon 选项,如kaylum's answer below 中所述。

尝试通过改代码解决

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

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*/

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

为什么我认为这个问题对于编程来说是普遍感兴趣的(这属于 ***)

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

软件

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

【问题讨论】:

因为包含一个 .c 文件。你如何得出这个结论? laplaafit_SOURCES 变量并没有告诉你这一点。事实上,这个错误告诉你一些不同的东西——它告诉你在 common.c 和 laplaafit.c 中定义了相同的变量。如果你只是打开这些文件,你可以很容易地在每个文件中看到相同的变量。所以与包含无关。 @toohonestforthissite 我并不是说没有问题。只是指出问题的诊断不正确。如果我们从错误的诊断开始,那么达到正确的解决方案就会更加困难。 您可以通过使用-fcommon 进行编译来解决此问题。来自gcc manual:-fcommon 将未初始化的全局变量放在一个公共块中。这允许链接器将不同编译单元中同一变量的所有暂定定义解析为同一对象,或解析为非暂定定义。这种行为与 C++ 不一致,并且在许多目标上意味着全局变量引用的速度和代码大小损失。主要用于使遗留代码无错误地链接。 旧 gcc 编译器的默认值是 -fcommon。这已从 gcc 10 更改为 -fno-common (这是您正在使用的)。因此,要么手动添加-fcommon,要么使用 10 之前的 gcc。 既然问题已经结束了,请您详细说明为什么?我相信我已经遵循了@toohonestforthissite 和@kaylum 关于如何通过以下方式改进问题的建议:1)删除我最初的错误诊断和 2)包括有关错误实际原因的详细信息(包括代码)以及 3 ) 如何通过更改代码来解决错误(在一个文件中将变量声明为extern)。在我进行这些更改后问题已关闭,所以我认为问题还有其他问题? 【参考方案1】:

如果源是使用-fcommon 构建的,则gcc 允许在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 标准。

【讨论】:

另一种选择是修复代码,这确实是不合格的。 @JohnBollinger 确实如此。答案已更新。 @JohnBollinger 正如我在评论中所写,IIRC 实际上是标准中的一个缺陷,看起来要在 C2x 中解决。我无法提供细节,因为我只是记忆模糊,我前段时间读过一些东西。也许这就是 gcc 改变默认行为的原因。如果这是真的,我希望这会破坏更多的代码,尤其是公司的遗留问题。会很有趣。 好的,检查过了。它已经在 C11 (n1570) 中。 6.9§5。看来我的记忆太微弱了,也许我实际上在一些 gcc 更改日志或错误报告中读到了这个问题,即反过来。因此,如果这是实际问题,那么错误确实存在于代码中。 @toohonestforthissite,是的,单一定义规则已经出现在迄今为止发布的每个 ISO C 版本中,一直到 C90。

以上是关于用现代 gcc 编译抛出错误(乘法定义)的旧 C 代码(在 Linux 上)? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

使用 avr-g++ 的 8b uC 中的 32b 乘法与使用 gcc 的 X86 上的 32b 乘法

gcc 可以用旧的第三方库编译 C++17 代码吗?

GCC C++11 调试构建,发布失败

在 GCC 中链接库

gcc编译汇编源码时怎样支持#define宏定义

用mingw32 gcc 一编译就出一大堆错误,应该怎么解决