看板 PttCurrent 關於我們 聯絡資訊
※ 引述《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.cmbbsd/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.owritemoney.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 5https://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://www.ptt.cc/bbs/PttCurrent/M.1640012985.A.9F7.html 簡言之: UTIL_OBJS= util_var.o # applepen CPROG_WITH_UTIL= \ toplazyBM writemoney \ # pineapplepen ${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