2020 年,项目历史记录中出现了多个定义错误。现在,如果不添加“extern”,就无法构建该点之前的提交

Multiple definitions errors showed up in project history in 2020. Now no commit before that point can be built without adding "extern"

提问人:Frotz 提问时间:1/2/2023 更新时间:1/3/2023 访问量:81

问:

自 2000 年以来,我一直在做的一个项目编译得很好,但会出现短暂的问题,一次可能有两到三个提交,这种情况非常罕见。早在 2020 年 2 月,构建就失败了,如下所示:

/usr/bin/ld: src/dumb/frotz_dumb.a(dinit.o):/home/foobar/src/dumb/dinit.c:26: multiple definition of `f_setup'; src/common/frotz_common.a(object.o):/home/foobar/src/common/object.c:23: first defined here

通过更改访问结构的源文件中的 to 实例,可以轻松修复它。很简单,对吧?但后来我回到了 2011 年,当时我终于开始在这个项目中使用 Git。编译失败的方式几乎相同:f_setup_t f_setup;extern f_setup_t f_setup;f_setup

gcc -O2 -DCONFIG_DIR="\"/usr/local/etc\""  -DVERSION="\"2.43\"" -DSOUND_DEV="\"\"" -DCOLOR_SUPPORT      -o src/dumb/dumb_input.o -c src/dumb/dumb_input.c
src/dumb/dumb_input.c:82:13: error: conflicting types for ‘getline’
   82 | static void getline(char *s)

在过去的两年里,我对此感到沮丧,因为,好吧,它现在构建和运行良好。但是现在我正在处理有人提交的问题以及两个构建日志:一个来自最新的提交,一个来自提交我上面描述的简单修复之前的提交。因此,我尝试使用新用户,然后是新的 Debian 机器,然后是新的 FreeBSD 机器来构建这些较旧的提交。我没有 macOS 计算机,而这正是问题提交者正在使用的。这个问题严重影响了我进行二分法以根除麻烦的能力。

我真的真的真的不想从 2011 年开始在这里和那里插入修复程序,从而弄得一团糟。我对发生的事情的唯一猜测是 2020 年左右 Debian 在 GCC 中发生了某种默认更改。所以。。。这到底是怎么回事?

我说的项目在这里:https://gitlab.com/DavidGriffith/frotz/。简单的修复发生在 https://gitlab.com/DavidGriffith/frotz/-/commit/b001e3f64a0f223136babb228f30fcac7fe09804

c git gitlab extern 未解析-外部

评论

0赞 Fe2O3 1/2/2023
回到DOS时代,我为一家有问题的软件公司工作。不记得最终解除并发打开文件数量的 DOS 版本。(DOS 3.0??)经理担心最新版本的产品使用了大量打开的文件,以及DOS 2.x客户端无法运行它的原因...原来是“旧DOS”客户端和“问题”客户端之间的强相关性......有时候(就像告别 Windows XP)你必须划一条线并说,“2011 年是十多年前。进化:“要么适应,要么灭亡......”
0赞 Frotz 1/2/2023
这种比较与这里发生的事情并不真正一致。对于初学者来说,尝试追踪 2019 年到现在之间某个时间开始的错误非常重要。
0赞 Jonathan Leffler 1/2/2023
我不会惊讶地发现头文件定义了变量,您现在正在使用 GCC 10 或更高版本,并且遇到了默认值从 to 的更改。如果是你的代码,请修复标头,使其不定义变量(它们在标头中都以前缀),并确保一个源文件定义每个声明的变量。如果它不是你的代码,请修改生成以包含该标志。-fcommon-fno-commonextern-fcommon
0赞 Eric Postpischil 1/2/2023
@JonathanLeffler:在“修复标题,以便它们不做 X(它们都是 Y)”形式的句子中,不清楚 Y 是用来做 X 还是 Y 是用来不做 X。
1赞 Lundin 1/2/2023
像这样的错误几乎总是由在头文件中定义变量或函数引起的。这反过来又可能导致各种与链接器相关的问题。从头文件中删除此类定义,并确保所有头都有头保护。具体来说,有一个同名的 POSIX 函数,不符合要求的 gcc 编译器将添加到该函数中,以破坏符合要求的程序。为了防止这种情况,请在符合 C 编译器模式下使用 gcc。getlinestdio.h-std=c17 -pedantic-errors

答:

3赞 Eric Postpischil 1/2/2023 #1

C 标准没有定义在多个翻译单元中使用的行为。这种形式的声明被称为暂定定义,尽管它实际上不是一个定义。但是,如果在翻译单元结束之前没有看到任何定义,则会导致创建定义。f_setup_t f_setup;

由于 C 开发的历史,这种形式的声明由不同的 C 实现以不同的方式处理。这是一个有记录的Unix行为,即由创建“通用”符号的暂定定义创建的定义在链接过程中被合并(同一“通用”符号的多个实例将链接到对单个实体的引用)。因此,在头文件中使用这些声明将起作用,而不会导致链接器错误。在其他 C 实现中,暂定定义将创建硬引用,从而导致有关多个定义的链接器错误。由于不同的 C 实现以不同的方式对待它们,因此 C 标准不需要一种或另一种行为;它使它未定义。

在 GCC 版本 10 之前,GCC 默认将暂定定义标记为“通用”符号。GCC 版本 10 中的默认值已更改。

要解决此问题,可以使用早于版本 10 的 GCC 版本,可以添加到编译命令中以选择旧行为,也可以添加到声明中,使它们成为不是定义的普通声明,而不是临时定义。在后一种情况下,您需要确保程序中的某个位置只有一个标识符定义。-fcommonextern

评论

0赞 Frotz 1/3/2023
这似乎巧妙地解释了这个问题。我将遵循 @Hasturkun 的建议,使用 的脚本功能来完成我的二分测试。git bisect