作者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/m.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