使用现代 gcc 编译会抛出错误(多次定义)的旧 C 代码(在 Linux 上)?
Compile old C code (on Linux) that throws errors (Multiply defined) with modern gcc?
我为什么要这个?
我想使用最初构建于 2007 年并根据变更日志最后更新于 2016 年的 C package。我想它会比以前编译得更干净。
遗憾的是,情况已不再如此。
错误
运行 ./configure
和 make
,我得到一个 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.c
和 common.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 中的局部数组的双指针=].如果函数为此获得 &Data
,data
实际上指的是原始全局数组,程序 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 年之间编译。我认为它在那时会编译得很干净。这有多个潜在问题:旧编译器是否仍适用于我的 2021 系统?这适用于现代系统中的图书馆吗?即使我成功了,生成的可执行文件是否会按照作者的预期运行?不过,这似乎是更可取的选择。
也有可能是我误解了错误或者误会了什么。
也有可能即使在 2007 年到 2016 年之间,使用我的编译器 (gcc) 也无法完全编译,并且作者使用了接受多个定义的不同编译器。
通过使用旧的编译器行为进行编译的解决方案
包括 -fcommon
选项,如 中所述。
尝试改代码解决
预期的行为显然是两个文件中的两个变量 Data
和 Size
引用相同的变量(内存中的相同点)。因此,在 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 标准。
我为什么要这个?
我想使用最初构建于 2007 年并根据变更日志最后更新于 2016 年的 C package。我想它会比以前编译得更干净。
遗憾的是,情况已不再如此。
错误
运行 ./configure
和 make
,我得到一个 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.c
和 common.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 中的局部数组的双指针=].如果函数为此获得 &Data
,data
实际上指的是原始全局数组,程序 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 年之间编译。我认为它在那时会编译得很干净。这有多个潜在问题:旧编译器是否仍适用于我的 2021 系统?这适用于现代系统中的图书馆吗?即使我成功了,生成的可执行文件是否会按照作者的预期运行?不过,这似乎是更可取的选择。
也有可能是我误解了错误或者误会了什么。
也有可能即使在 2007 年到 2016 年之间,使用我的编译器 (gcc) 也无法完全编译,并且作者使用了接受多个定义的不同编译器。
通过使用旧的编译器行为进行编译的解决方案
包括 -fcommon
选项,如
尝试改代码解决
预期的行为显然是两个文件中的两个变量 Data
和 Size
引用相同的变量(内存中的相同点)。因此,在 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.
或者正如@JohnBollinger 所指出的,另一种选择是修复代码以删除那些多个定义并使代码严格符合 C 标准。