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

Compile old C code (on Linux) that throws errors (Multiply defined) with modern gcc?

我为什么要这个?

我想使用最初构建于 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);common.c 中调用函数 int load() 读取数组 *Data 并确定其长度 Size).

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

编辑:因为有人在评论中提出 load(&Data,&Size,infile); “远非定义”,我想我应该更详细一点。

load(&Data,&Size,infile);

common.c

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

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

*size (为此函数得到&Size)是地址&Size中的值所以其他全局变量。

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

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

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

此外,评论说我实际上并不了解足够的 C 来诊断程序,因此我宁愿聘请了解的人。这会将 Whosebug 中允许 post 的门槛提高到一个非常高的水平;在通常 posted 并被视为完全可以接受的问题中并不总是达到这一水平。这实际上可能是一个合理的建议,但它不会让我有任何地方可以提出我可能对 C 或任何其他语言提出的问题。如果评论的作者对此很认真,可能值得 post 在 Meta 中使用并建议将 Whosebug 一分为二,一个给专家,一个给其他人。

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

据我所知,有两种方法可以解决这个问题:

也有可能是我误解了错误或者误会了什么。

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

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

包括 -fcommon 选项,如 中所述。

尝试改代码解决

预期的行为显然是两个文件中的两个变量 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 实现)。

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

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

软件

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

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

The -fcommon places uninitialized global variables in a common block. This allows the linker to resolve all tentative definitions of the same variable in different compilation units to the same object, or to a non-tentative definition. This behavior is inconsistent with C++, and on many targets implies a speed and code size penalty on global variable references. It is mainly useful to enable legacy code to link without errors.

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

GCC now defaults to -fno-common. As a result, global variable accesses are more efficient on various targets. In C, global variables with multiple tentative definitions now result in linker errors. With -fcommon such definitions are silently merged during linking.

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

之前的 gcc 版本

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