作者IepID (Iep Iweidieng)
看板PttCurrent
标题Re: [问题] kubuntu x86_64编译错误 变数重复定义
时间Mon Dec 20 23:09:22 2021
※ 引述《knko ()》之铭言:
: 我在使用kubuntu(x86_64)编译此套软体时执行pmake all install时发现错误,原因是/u
: sr/bin/ld: /tmp/writemoney-42e226.o:/home/bbs/pttbbs/util/writemoney.c:5: multip
: le definition of `now'; util_var.o:/home/bbs/pttbbs/util/../mbbsd/var.c:374: fir
: st defined here,是於pttbbs.conf中有加入#define SHMALIGNEDSIZE (1048576*4)
: #define TIMET64,似乎是爲重复定义但是我不确定是软体还是我设定的问题,我使用的版
: 本最後的编辑是https://github.com/ptt/pttbbs/commit/576513c502a9bf5fcfa08ae52ee94
: ed0c67be608
: ----
: Sent from BePTT on my Samsung SM-M127F
此问题与在
pttbbs.conf 中使用了哪些 `
#define`s 无关。
「重复定义」
────────────────────────────────
‧ util/writemoney.c: Line 5–6:
time4_t now; // C: Tentative definition // C++: 变数定义
extern SHM_t *SHM; // 变数宣告 (
不建立物件)
‧ include/var.h: Line 96–100:
extern time4_t now; // 变数宣告 (
不建立物件)
extern int KEY_ESC_arg;
extern int watermode ;
extern int wmofo ;
extern SHM_t *SHM; // 变数宣告 (
不建立物件)
‧ mbbsd/var.c: Line 373–375:
/* io.c */
time4_t now; // C: Tentative definition // C++: 变数定义
int KEY_ESC_arg; // C: Tentative definition // C++: 变数定义
Tentative Definition
────────────────────────────────
Tentative definition 是 C 特有的概念,
指既能作
宣告也能作
定义的
函式外变数宣告或定义。
大部份的既没有使用 `
extern`
且没有用 `
=` 初始化的函式外
变数宣告/定义属於此类。
(函式没有 tentative definition,而且函式宣告预设会被视爲已有 `
extern`)
(注意:将 tentative definition 加上 `
static` 後
仍爲 tentative definition)
C++ 中无此概念,这样写的宣告/定义会被视爲定义,会建立被初始化爲 0 的物件;
而要进行函式外变数宣告时,则必须使用 `
extern` 关键字
(可搭配匿名 `
namespace` 以限制被宣告的变数的作用域爲目前档案)。
由於 C++ 有
one definition rule,一个变数/函式在整个程式中仅能有
<= 1 个定义,
因此若编译器发现单一个「
档案」(严谨地说,是
translation unit)
具有 > 1 个这样的变数定义时,会发出
编译时期错误。
(上述程式码中并没有这样的问题)
C 对 tentative definition 有特别的处理规则:
如果编译器编译这个档案时,未找到任何此变数的
只能作定义的定义,
才会爲此宣告/定义建立物件。
建置失败的过程
────────────────────────────────
util/writemoney.c 与
mbbsd/var.c 间,没有一方被另一方 `
#include`d 的关系;
编译器编译其中一个时,看不到另一个档案中的定义,因此
并不会发出编译时期错误。
您所遇到的建置错误是
连结时期错误 (link-time error),
而不是
编译时期错误 (compile-time error)。
问题来了,
「重复定义」的连结
────────────────────────────────
‧ util/Makefile:
‧ Line 11–29:
UTIL_OBJS= util_var.o # 编译後的
var.c(含有 `
now` 的定义)
(空行)
MBBSD_OBJS= var #
var.c
(空行)
# 下面这些程式, 会被 compile 并且和 $(UTIL_OBJS) 联结 # 联 → 连
CPROG_WITH_UTIL= \
(Line 17–20 略)
toplazyBM writemoney \ #
writemoney.c(含有 `
now` 的定义)
(Line 22–27 略)
munin useractive_munin \
(空行)
‧ Line 67–70:
.for fn in ${MBBSD_OBJS} #
${fn}.c ==
var.c
util_${fn}.o: ${BBSBASE} $(SRCROOT)/mbbsd/${fn}.c # 产生
util_var.o
${CC} ${CFLAGS} -D_BBS_UTIL_C_ -c -o $@ $(SRCROOT)/mbbsd/${fn}.c
.endfor
‧ Line 57–60:
.for fn in ${CPROG_WITH_UTIL} #
${fn}.c 会 ==
writemoney.c
${fn}: ${BBSBASE} ${fn}.c ${UTIL_OBJS} #
${UTIL_OBJS} ==
util_var.o
${CC} ${CFLAGS} ${LDFLAGS} -o ${fn} ${UTIL_OBJS} ${fn}.c $(LDLIBS)
.endfor
红色的行中,
${CC} (`
gcc` 或 `
clang`) 会去执行编译器与连结器。
其中,连结器在连结
util_var.o 与
writemoney.c 时,
会发现 `
now` 具有两个 tentative definitions 的问题。
-fcommon & -fno-common
────────────────────────────────
`
-fcommon` 选项(不可用於
动态连结库 (以 `--shared` 选项编译的))
会使编译器发现某变数具有 tentative definition(s) 但未找到只能作定义的定义时,
不直接建立对应物件,而到连结器连结时
才爲此变数的所有 tentative definition(s) 建立单一个
被初始化爲 0 的对应物件。
`
-fno-common` 选项(从
GCC 10 开始的预设值)
则会使编译器发现某个档案有这样的状况时,
就直接建立被初始化爲 0 的物件,使得连结器连结时发出连结时期错误。
ISO C 仅要求一个变数在整支程式中只能有 <= 1 个
被初始化的定义,
但
未规定 > 1 个档案具有 tentative definition(s) 时要如何处理。
因此 ISO C 并
不保证这样的程式能够被正确地建置或执行;
这样的程式可能会随实际的建置环境不同,而有不同的建置或执行结果。
也就是说,在 ISO C
标准中,这会造成
未定义行爲 (undefined behavior)。
只是 GCC
额外提供了编译选项,使得这种情况在
标准之外变得
有定义。
此外,由於 `
-fcommon` 只对 tentative definition 有效,
如果 > 1 个档案具有只能作定义的定义,或是以 C++ 模式编译的话,
使用 `
-fcommon` 选项编译并
无法避免连结时期错误。
※ 引述《holishing ( )》之铭言:
: 新版的 gcc 会严格限制 multiple definition
: 所以在 Ubuntu Focal 或 Debian Bullseye 会遇到编译错误 (以前只会警告)
: 两种解法:
: 第一种是在编译参数加上 -fcommon (让它允许重复定义)
: 第二种是把重复定义删掉,例如参考以下修改:
: https://github.com/bbsdocker/imageptt/blob/87c0ec3c/multipledef.patch
: 应该就可以编译过了
根本的解决方法
────────────────────────────────
1. 直接移除此 tentative definition。
‧ 因已由 `
#include "bbs.h"` → `
#include "var.h"` 引入宣告,
此 tentative definition 并非必要。
2. 若要保留此宣告,须加上 `
extern`:
‧ util/writemoney.c: Line 5–6:
- time4_t now; // C: Tentative definition // C++: 定义
+ extern time4_t now; // 宣告 (不建立物件)
extern SHM_t *SHM; // 宣告 (
不建立物件)
爲什麽使用了 tentative definition?
────────────────────────────────
在 ISO C89 以前,C 没有 `
extern` 可用,只能用此方法进行前向变数宣告;
而 ISO C89 引入 `
extern` 後,爲了维持向旧相容,才引入了这项规则。
见
https://en.cppreference.com/w/c/language/extern
或许
writemoney.c 中的 `
now` 的 tentative definition 的意图是进行变数宣告。
也或许只是因爲当时的编译器不会提出错误讯息,所以才没有加上 `
extern`。
‧ Google 搜寻在 1997 年推出
(当时最流行的浏览器是 Netscape Navigator):
https://web.archive.org/web/19981111183552/http://google.stanford.edu/
‧ Wikipedia 在 2001 年推出
(最流行 Microsoft Internet Explorer 5
):
https://web.archive.org/web/20010727112808/http://www.wikipedia.org/
‧ cppreference.com 也在 2001 年推出:
https://web.archive.org/web/20010223150424/http://www.cppreference.com/
‧
writemoney.c 是在 2005 年撰写的
(最流行 Microsoft Internet Explorer 6):
https://github.com/ptt/pttbbs/blob/814f94b737/util/writemoney.c
当时使用浏览器浏览网页,甚至使用搜寻引擎,或许还不是很流行的事情。
当时的开发者或许没有如 cppreference.com 般唾手可得的语言标准参考资料能够参考。
话虽如此,以上也只是对 16 年前的情况的猜测。也许眞相早已随着时光而流逝。
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 140.116.130.29 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/PttCurrent/M.1640012985.A.9F7.html
简言之:
UTIL_OBJS= util_var.o # apple
pen
CPROG_WITH_UTIL= \ toplazyBM writemoney \ # pineapple
pen
> ${CC} ${CFLAGS} ${LDFLAGS} -o ${fn} ${UTIL_OBJS} ${fn}.c $(LDLIBS)<
# PENPINEAPPLEAPPLEPEN
※ 编辑: IepID (140.116.130.29 台湾), 12/20/2021 23:24:55
※ 编辑: IepID (140.116.130.29 台湾), 12/20/2021 23:29:24
※ 编辑: IepID (140.116.130.29 台湾), 12/20/2021 23:36:32
※ 编辑: IepID (140.116.130.29 台湾), 12/20/2021 23:44:26
※ 编辑: IepID (140.116.130.29 台湾), 12/20/2021 23:46:00