【GCC編譯優(yōu)化系列】這種讓人看不懂的multiple-definition真的有點(diǎn)讓人頭疼
1 寫(xiě)在前面
有印象的朋友應(yīng)該記得我之前寫(xiě)過(guò)一篇 關(guān)于GCC編譯報(bào)錯(cuò)及對(duì)應(yīng)解決辦法,在該文的 3.5.3 章節(jié)有提到幾種很典型的 multiple-definition 鏈接錯(cuò)誤,也簡(jiǎn)要分析了其出現(xiàn)問(wèn)題的原因及對(duì)應(yīng)解決方法。
multiple-definition 在GCC編譯報(bào)錯(cuò)里面,它的報(bào)錯(cuò)本質(zhì)是 重復(fù)定義,可能是函數(shù)重復(fù)定義,也可能是變量重復(fù)定義。
但今天我要介紹的這個(gè) multiple-definition 跟常規(guī)遇到的還不太一樣,否則這個(gè)問(wèn)題就不值得我寫(xiě)篇文章來(lái)做記錄了,詳細(xì)請(qǐng)看下文。
2 問(wèn)題描述
事情是這樣的,前幾天一個(gè)同事給我報(bào)了一個(gè)我們SDK的問(wèn)題,我想著加快復(fù)現(xiàn)問(wèn)題,于是我找了他要他的應(yīng)用代碼,拿到我的編譯環(huán)境環(huán)境來(lái)編譯復(fù)現(xiàn)。
結(jié)果,好巧不巧,拿他代碼一編譯,居然給我報(bào)錯(cuò)了,而且這個(gè)報(bào)錯(cuò)把我整不會(huì)了!朋友,請(qǐng)看:
/home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:76: multiple definition of `mcu_ota_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:76: first defined here
/home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:70: multiple definition of `notify_state_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:70: first defined here
/home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:60: multiple definition of `wifi_state_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:60: first defined here
/home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:15: multiple definition of `frame_num_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:15: first defined here
collect2: error: ld returned 1 exit status
里面error提示的 multiple definition 異常亮眼,但是又讓人摸不著頭腦,這有點(diǎn)不按常理出牌!
要知道,他的應(yīng)用代碼明明都可以release版本的呀,而我的編譯環(huán)境肯定也沒(méi)有問(wèn)題,畢竟 sample app 在我這都是可以編譯通過(guò)的,所謂我大膽推測(cè)問(wèn)題很有可能出在他們的應(yīng)用代碼上,而編譯報(bào)錯(cuò)也的確提示是應(yīng)用代碼的問(wèn)題。
3 場(chǎng)景復(fù)現(xiàn)
為了準(zhǔn)確描述這個(gè)問(wèn)題,排除其他的排除干擾因素,我把相關(guān)C代碼和頭文件捋一下。 整個(gè)應(yīng)用部分的工程包括2個(gè)C代碼和2個(gè)頭文件:
主要處理應(yīng)用入口的app_entry.c:
/* app_entry.c */
#include "sdk.h" //SDK的統(tǒng)一頭文件
#include "app_entry.h" //app_entry的頭文件
#include "user_app.h" //user_app的頭文件
/* called by lower SDK */
int app_entry_main(void)
{
/* some code */
/* call user_app */
user_app_init();
/* some code */
return 0;
}
appentry對(duì)應(yīng)的頭文件appentry.h:
#ifndef __APP_ENTRY_H__
#define __APP_ENTRY_H__
/* external functions */
extern int app_entry_main(void);
#endif /* end of __APP_ENTRY_H__ */
主要處理用戶(hù)應(yīng)用邏輯的user_app.c:
/* user_app.c */
#include "sdk.h" //SDK的統(tǒng)一頭文件
#include "app_entry.h" //app_entry的頭文件
#include "user_app.h" //user_app的頭文件
/* other functions */
/* called by app_entry */
int user_app_init(void)
{
/* some code */
return 0;
}
userapp對(duì)飲的頭文件userapp.h:
#ifndef __USER_APP_H__
#define __USER_APP_H__
/* some enum definition */
enum {
UART_FRAME_1 = 0x01,
UART_FRAME_2,
UART_FRAME_3,
UART_FRAME_4,
UART_FRAME_5,
} frame_num_t;
enum {
WIFI_STATE_1 = 0x01,
WIFI_STATE_2,
WIFI_STATE_3,
WIFI_STATE_4,
WIFI_STATE_5.
} wifi_state_t;
enum {
NOTIFY_STATE_1 = 0x01,
NOTIFY_STATE_2,
NOTIFY_STATE_3,
NOTIFY_STATE_4,
NOTIFY_STATE_5,
} notify_state_t;
enum {
MCU_OTA_NO_BIN = 0x00,
MCU_OTA_DOWNLOAD_OK,
MCU_OTA_DOWNLOAD_FAIL,
} mcu_ota_t;
/* external functions */
extern int user_app_init(void);
#endif /* end of __USER_APP_H__ */
另外,補(bǔ)充說(shuō)明一下,我們使用的是交叉編譯工具是針對(duì)RISCV架構(gòu)的 riscv64-unknown-gcc。
簡(jiǎn)化之后,應(yīng)用代碼大概就是如上面所示,就這樣的代碼給報(bào)錯(cuò)了,有點(diǎn)納悶。
4 深入分析
4.1 可能性分析
頭文件被重復(fù)包含了?
我看到這個(gè)報(bào)錯(cuò)的第一反應(yīng)是,難道頭文件被重復(fù)包含了?
比如在某個(gè)頭文件中定義了一個(gè)變量(假設(shè)真有這么寫(xiě)的),如果它的頭文件沒(méi)有按照標(biāo)準(zhǔn)的 ifndef 的那種寫(xiě)法來(lái)寫(xiě),那么當(dāng)這個(gè)頭文件被一個(gè)C文件直接或間接包含多次的時(shí)候,這個(gè)定義的變量就會(huì)存在多個(gè)副本,這個(gè)時(shí)候就會(huì)報(bào) “multiple definition”。
可是,我仔細(xì)檢查過(guò)user_app.h的頭部寫(xiě)法,是正確的,不存在這種問(wèn)題。
某個(gè)C文件里面存在多個(gè)xxx_t的副本?
這一種也是可能的,比如a.h中定義了一個(gè)xxx_t,然后b.h中也定義了同名的xxx_t,這時(shí)候某個(gè)C文件同時(shí)包含了a.h和b.h,那么xxx_t在這個(gè)C文件中就有兩個(gè)定義。
這個(gè)時(shí)候,通過(guò)查看預(yù)處理后的文件(.i)文件就可以看得出來(lái),是否存在這種情況。
如何打開(kāi)生成預(yù)編譯后的文件,可以參考 這篇文章的 4.2.2 章節(jié)介紹。
以本案例中的 mcu_ota_t 為例,很顯然,并不存在這種情況,只有一個(gè)定義呢。
xxx@ubuntu:~/user_app$
xxx@ubuntu:~/user_app$ find . -name user_app.i
./out/user_app@xxxevb/modules/home/xxx/user_app/user_app.i
xxx@ubuntu:~/user_app$ cat ./out/user_app@xxxevb/modules/home/xxx/user_app/user_app.i | grep -nw mcu_ota_t
5547:}mcu_ota_t;
xxx@ubuntu:~/user_app$
4.2 分析map文件
既然是 multiple definition,那么我搜搜看!
給我上 grep大法,不搜不知道,一搜嚇一跳。以 mcuotat 為例:
xxx@ubuntu:~/user_app$ grep -rsnw mcu_ota_t
user_app.h:77:}mcu_ota_t;
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.i:5547:}mcu_ota_t;
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2488: .globl mcu_ota_t
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2554: .section .sbss.mcu_ota_t,"aw",@nobits
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2555: .type mcu_ota_t, @object
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2556: .size mcu_ota_t, 1
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2557:mcu_ota_t:
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:3361: .4byte mcu_ota_t
out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:9661: .string "mcu_ota_t"
Binary file out/user_app@xxxevb/modules/home/xxx/user_app/user_app.o matches
Binary file out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.o matches
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.i:3807:}mcu_ota_t;
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:87: .globl mcu_ota_t
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:94: .section .sbss.mcu_ota_t,"aw",@nobits
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:95: .type mcu_ota_t, @object
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:96: .size mcu_ota_t, 1
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:97:mcu_ota_t:
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:574: .4byte mcu_ota_t
out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:1136: .string "mcu_ota_t"
out/user_app@xxxevb/binary/user_app@xxxevb.map:1811: .sbss.mcu_ota_t
out/user_app@xxxevb/binary/user_app@xxxevb.map:1879: .sbss.mcu_ota_t
out/user_app@xxxevb/binary/user_app@xxxevb.map:47777:mcu_ota_t /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o)
Binary file out/user_app@xxxevb/libraries/user_app.a matches
Binary file out/user_app@xxxevb/libraries/user_app.stripped.a matches
map文件清晰地顯示,在BSS段中有個(gè)object叫 mcuotat,要知道在BSS段中出現(xiàn),這玩意就是global的東西了。
這什么意思?
意思就是編譯器已經(jīng)把mcuotat當(dāng)做一個(gè) 全局變量 了。
那么我們來(lái)梳理一下,當(dāng)userapp.h里面定義了一個(gè) mcuotat 的全局變量,這個(gè)userapp.h同時(shí)被appentry.c和userapp.c包含,自然在這兩個(gè)C文件中,都有這個(gè)mcuotat全局變量的副本存在;那么根據(jù) 【經(jīng)驗(yàn)總結(jié)】一文帶你了解C代碼到底是如何被編譯的 提及的,在鏈接階段,編譯器就會(huì)去查找并鏈接它們,這個(gè)時(shí)候多個(gè)同名全局變量,肯定是不允許的,自然而然,就報(bào)了 “multiple definition” 錯(cuò)誤。
4.3 扒一扒基礎(chǔ)語(yǔ)法
為此,我特意去查了一下C語(yǔ)言教科書(shū),找了一些關(guān)于C語(yǔ)言的枚舉定義的介紹,再學(xué)習(xí)了一下。
果然userapp.h中的那幾個(gè) xxxt 枚舉并不是一種規(guī)范寫(xiě)法,倒不是說(shuō)不可以這么寫(xiě),只是這樣寫(xiě)之后容易造成干擾,嚴(yán)重的情況下還會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤。
相關(guān)學(xué)習(xí)資料,可以看 參考鏈接 附錄里面的文章。
4.4 GCC的版本差異
那么問(wèn)題來(lái)了,為何同事的編譯環(huán)境沒(méi)報(bào)錯(cuò),而我的編譯環(huán)境報(bào)錯(cuò)了呢?
原來(lái),前段時(shí)間我們的SDK因一個(gè)廠(chǎng)商私有庫(kù)比較新,特意升級(jí)了GCC的版本,由 riscv64unkownelfgcc8.3.0 升級(jí)到了 riscv64unkownelfgcc10.2.0,而應(yīng)用那邊還沒(méi)來(lái)得及升級(jí)這個(gè)版本。所以才造成了這樣的沖突。
至于為何兩個(gè)版本有差異呢?后面我也會(huì)提到,其實(shí)8.3.0版本對(duì)這種寫(xiě)法是有報(bào) 警告 的,而10.2.0版本是報(bào) 錯(cuò)誤 的;在我們的編譯環(huán)境中,除編譯器版本不一樣外,其他由構(gòu)建層傳入的所有編譯選項(xiàng)都是一模一樣的。
那么,下面分析下究竟的差異在哪里。
4.4.1 對(duì)比map文件和匯編代碼
如下圖所示:
匯編文件顯示,兩者編譯出來(lái)的段分布是不一樣的,一個(gè)在.common段,一個(gè)在.global段;
而map文件中,在.global段的被分配到了 .sbss段中,作為全局的object而存在;所以就報(bào)了 mutiple definiton 的錯(cuò)誤。
這個(gè)簡(jiǎn)單分析,基本就可以確定是在編譯階段引入的問(wèn)題,而不是在鏈接階段引入的問(wèn)題,所以后面的排查中,應(yīng)重點(diǎn)關(guān)注編譯選項(xiàng),而不是鏈接選項(xiàng)。
4.4.2 如何查看GCC默認(rèn)使用的編譯選項(xiàng)
如何你將 “**” 這幾個(gè)關(guān)鍵字去搜索,你很大概率拿到的是這個(gè)鏈接,它的方法是這樣的:
echo "" | gcc -v -x c++ -E -
然后查看輸出的內(nèi)容中的:COLLECTGCCOPTIONS
對(duì)應(yīng)我這邊,替換掉對(duì)應(yīng)的gcc版本,8.2.0和10.3.0版本的輸出分別是:
GCC8.3.0 COLLECT_GCC_OPTIONS='-v' '-E' '-march=rv64imafdc' '-mabi=lp64d'
GCC10.2.0 COLLECT_GCC_OPTIONS='-v' '-E' '-march=rv64imafdc' '-mabi=lp64d' '-march=rv64imafdc'
眼看,壓根看不出差異,對(duì)不對(duì)。那我倒懷疑是方法有問(wèn)題。
我想起之前寫(xiě)過(guò) 一篇文章關(guān)于GCC默認(rèn)鏈接選項(xiàng) 的,里面倒是提到了取默認(rèn)參數(shù)的蛛絲馬跡,立馬實(shí)踐下。
這時(shí)候你先準(zhǔn)備一個(gè)簡(jiǎn)單得不能再簡(jiǎn)單的helloworld.c:
#include
int main(void)
{
printf("hello world\r\n");
return 0;
}
然后在對(duì)應(yīng)的目錄執(zhí)行(注意替換gcc的路徑):
arm-none-gcc -v -Q hello.c
這個(gè)方法是我自己實(shí)踐摸索總結(jié)出來(lái)的參數(shù)組合,全網(wǎng)估計(jì)還沒(méi)人這么用!
這個(gè)方法可以順利取得GCC默認(rèn)使能的參數(shù),留意輸出的 options enabled 即可!
4.4.3 對(duì)比GCC的默認(rèn)使能的編譯選項(xiàng)
為了深究這個(gè)報(bào)錯(cuò)問(wèn)題,我使用關(guān)鍵字 "mutiple definition 10.2.0",找到這么一個(gè) 有效鏈接,里面描述的情景,基本跟我的差不多。
摘抄里面的一段話(huà),理解下:
The issue can be fixed with adding
-fcommon
to compiler options.
A common mistake in C is omitting extern when declaring a global variable in a header file. If the header is included by several files it results in multiple definitions of the same variable. In previous GCC versions this error is ignored. GCC 10 defaults to -fno-common, which means a linker error will now be reported. To fix this, use extern in header files when declaring global variables, and ensure each global is defined in exactly one C file. If tentative definitions of particular variables need to be placed in a common block, attribute((common)) can be used to force that behavior even in code compiled without -fcommon. As a workaround, legacy C code where all tentative definitions should be placed into a common block can be compiled with -fcommon.
順著這個(gè)編譯選項(xiàng),我找到了GCC 10.x版本的 編譯選項(xiàng)在線(xiàn)說(shuō)明文檔,摘抄下里面關(guān)于 -fcommon 選項(xiàng) 和 -fno-common 選項(xiàng)的說(shuō)明,大家理解下:
-fcommon
In C code, this option controls the placement of global variables defined without an initializer, known as tentative definitions in the C standard. Tentative definitions are distinct from declarations of a variable with the
extern
keyword, which do not allocate storage.The default is -fno-common, which specifies that the compiler places uninitialized global variables in the BSS section of the object file. This inhibits the merging of tentative definitions by the linker so you get a multiple-definition error if the same variable is accidentally defined in more than one compilation unit.
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.
回過(guò)頭來(lái),根據(jù)前面取得的默認(rèn)編譯參數(shù),我們對(duì)比下兩個(gè)GCC版本的默認(rèn)選項(xiàng),我們果然發(fā)現(xiàn)了 -fcommon 有差別.
左邊是8.3.0版本,它默認(rèn)使能了 -fcommon 這個(gè)參數(shù)就決定了 mcuotat 編譯到 .common 段;從而鏈接的時(shí)候,并不會(huì)報(bào)警告,而僅僅是報(bào)了一個(gè) warning: multiple common of 警告。
而右邊的10.2.0版本沒(méi)有 -fcommon,根據(jù)在線(xiàn)說(shuō)明可知,10.x版本默認(rèn)是關(guān)閉了該選項(xiàng),即使用的是 -fno-common,所以 mcuotat 編譯到了 .global 段;這就直接導(dǎo)致在鏈接的時(shí)候,報(bào)了 mutiple-definiton 錯(cuò)誤,因?yàn)槲挥?.global 段是不能有多份一樣的定義。
按照這個(gè)分析,在10.2.0版本中,手動(dòng)加上 -fcommon 選項(xiàng),編譯也不會(huì)報(bào) mutiple-definiton 錯(cuò)誤。
是否真是如此,留個(gè)小疑問(wèn),有心讀者可以自行驗(yàn)證驗(yàn)證。
4.4.4 得出結(jié)論
綜上幾個(gè)步驟下來(lái),基本可以得出一個(gè)結(jié)論,外圍調(diào)用GCC發(fā)起編譯、鏈接等能看得見(jiàn)的步驟里,兩個(gè)版本的參數(shù)都是一模一樣的,很顯然不是因?yàn)樯蠈觽魅氲木幾g選項(xiàng)導(dǎo)致的;經(jīng)過(guò)精準(zhǔn)地資料輔助分析,得出是 GCC 10.2.0 版本默認(rèn)使用的 -fno-common 選項(xiàng)惹的禍,但它的本意初衷是好的,只不過(guò)不被程序猿所熟知而已。
一個(gè)看似簡(jiǎn)單的 mutiple-definiton 問(wèn)題,繞了一圈,終于發(fā)現(xiàn)、理解并有效解決地解決這個(gè)問(wèn)題。
5 修復(fù)驗(yàn)證
5.1 問(wèn)題修復(fù)
明白了上面的基礎(chǔ)語(yǔ)法和GCC的編譯特性之后,修復(fù)的方法就很簡(jiǎn)單了,只需要把 user_app.h 中所有的枚舉定義加上一個(gè) typedef,正如 C語(yǔ)言--enum,typedef enum 枚舉類(lèi)型詳解 所介紹的方法三那樣。
修改后的代碼如下:
#ifndef __USER_APP_H__
#define __USER_APP_H__
/* some enum definition */
typedef enum {
UART_FRAME_1 = 0x01,
UART_FRAME_2,
UART_FRAME_3,
UART_FRAME_4,
UART_FRAME_5,
} frame_num_t; //注意:此處的frame_num_t為枚舉型enum frame_num_t的別名
typedef enum {
WIFI_STATE_1 = 0x01,
WIFI_STATE_2,
WIFI_STATE_3,
WIFI_STATE_4,
WIFI_STATE_5.
} wifi_state_t; //注意:此處的wifi_state_t為枚舉型enum wifi_state_t的別名
typedef enum {
NOTIFY_STATE_1 = 0x01,
NOTIFY_STATE_2,
NOTIFY_STATE_3,
NOTIFY_STATE_4,
NOTIFY_STATE_5,
} notify_state_t; //注意:此處的notify_state_t為枚舉型enum notify_state_t的別名
typedef enum {
MCU_OTA_NO_BIN = 0x00,
MCU_OTA_DOWNLOAD_OK,
MCU_OTA_DOWNLOAD_FAIL,
} mcu_ota_t; //注意:此處的mcu_ota_t為枚舉型enum mcu_ota_t的別名
/* external functions */
extern int user_app_init(void);
#endif /* end of __USER_APP_H__ */
主要的核心修改,就是把enum的寫(xiě)法糾正了,我跟對(duì)應(yīng)的應(yīng)用開(kāi)發(fā)的童鞋聊過(guò),他說(shuō)可能就是寫(xiě)代碼的時(shí)候 偷懶 了點(diǎn),壓根沒(méi)寫(xiě)到這樣的寫(xiě)法有啥不妥,最最最重要的是 riscv64unkownelf_gcc8.3.0 的默認(rèn)編譯參數(shù),放任了這種有問(wèn)題的寫(xiě)法(僅僅是編譯警告,而不是編譯錯(cuò)誤),從而沒(méi)有在第一時(shí)間暴露出來(lái),造成代碼的語(yǔ)法隱患。
riscv64unkownelf_gcc8.3.0 版本的編譯輸出,注意其實(shí)這里是有 警告 的!
Making user_app@xxxevb.elf
/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `mcu_ota_t'
/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `notify_state_t'
/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `wifi_state_t'
/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `frame_num_t'
Making user_app@xxxevb.bin
Making user_app@xxxevb.hex
5.2 問(wèn)題驗(yàn)證
代碼修復(fù)之后,使用 riscv64unkownelfgcc8.3.0 版本的GCC和 riscv64unkownelfgcc10.2.0版本的GCC,均一次編譯通過(guò),這才是正統(tǒng)的C語(yǔ)言寫(xiě)法,容不得半點(diǎn)偷懶啊!
同時(shí),我們?cè)俜治鱿聠?wèn)題修復(fù)之后,map文件里面對(duì)這幾個(gè)定義的變化,以 mcuotat 為例:
如同我們所預(yù)料的,加上typedef之后,這個(gè)mcuotat已經(jīng)是一個(gè)枚舉類(lèi)型的別名,并不是一個(gè)變量,自然在map文件肯定找不到它,但是原來(lái)的那種寫(xiě)法能找到的原因是,它那種是定義了一個(gè)全局變量叫 mcuotat。這才是兩者的本質(zhì)區(qū)別。
6 經(jīng)驗(yàn)總結(jié)
- 嚴(yán)謹(jǐn)地寫(xiě)好每一行代碼:了解每一行代碼背后的基礎(chǔ)語(yǔ)法,溫故而知新。
- 對(duì)比確認(rèn)是個(gè)好方法:選擇適當(dāng)?shù)谋容^方法,找出差異,往往差異的地方就是解決問(wèn)題的突破口。
- 回歸問(wèn)題的本質(zhì):暫且認(rèn)為 編譯器的報(bào)錯(cuò)是不會(huì)騙人的,在這個(gè)基礎(chǔ)之上,逐步從問(wèn)題報(bào)錯(cuò)的表面往里面深究,為何會(huì)是 “multiple definition”,何時(shí)才會(huì)出現(xiàn)這種錯(cuò)誤?
- typedef 是個(gè)好東西,用好它:熟悉它的基礎(chǔ)語(yǔ)法,每一種寫(xiě)法的搭配代表什么含義,理解并應(yīng)用它,很重要。
- 認(rèn)真對(duì)待每一個(gè)編譯器提示的 編譯警告:保不準(zhǔn)這些警告哪天就把你帶入坑里,使用GCC的-Werror是個(gè)好選擇,把警告當(dāng)錯(cuò)誤處理,有助于你寫(xiě)出更為嚴(yán)謹(jǐn)?shù)拇a。
- GCC的默認(rèn)編譯參數(shù):這個(gè)了解非常有必要,不然下次遇到好端端的代碼編譯不過(guò),就沒(méi)轍了。
7 參考鏈接
- 【經(jīng)驗(yàn)科普】實(shí)戰(zhàn)分析C工程代碼可能遇到的編譯問(wèn)題及其解決思路
- 【經(jīng)驗(yàn)總結(jié)】一文帶你了解C代碼到底是如何被編譯的
- 【C語(yǔ)言之結(jié)構(gòu)體】如何定義結(jié)構(gòu)體并定義結(jié)構(gòu)體變量
- 【C語(yǔ)言之枚舉】如何定義枚舉并定義枚舉變量
- 【C語(yǔ)言之typedef】typedef的基本用法
8 更多分享
歡迎關(guān)注我的github倉(cāng)庫(kù)01workstation,日常分享一些開(kāi)發(fā)筆記和項(xiàng)目實(shí)戰(zhàn),歡迎指正問(wèn)題。
同時(shí)也非常歡迎關(guān)注我的CSDN主頁(yè)和專(zhuān)欄:
【CSDN主頁(yè):架構(gòu)師李肯】
【RT-Thread主頁(yè):架構(gòu)師李肯】
【C/C++語(yǔ)言編程專(zhuān)欄】
【GCC專(zhuān)欄】
【信息安全專(zhuān)欄】
【RT-Thread開(kāi)發(fā)筆記】
【freeRTOS開(kāi)發(fā)筆記】
有問(wèn)題的話(huà),可以跟我討論,知無(wú)不答,謝謝大家。
審核編輯:湯梓紅
-
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7614瀏覽量
137726 -
GCC
+關(guān)注
關(guān)注
0文章
108瀏覽量
24889 -
編譯
+關(guān)注
關(guān)注
0文章
661瀏覽量
33046 -
definition
+關(guān)注
關(guān)注
0文章
5瀏覽量
6996
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論