0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

【GCC編譯優(yōu)化系列】multiple-definition

嵌入式物聯(lián)網(wǎng)開發(fā) ? 來(lái)源:嵌入式物聯(lián)網(wǎng)開發(fā) ? 作者:嵌入式物聯(lián)網(wǎng)開發(fā) ? 2022-07-11 09:26 ? 次閱讀

【GCC編譯優(yōu)化系列】這種讓人看不懂的multiple-definition真的有點(diǎn)讓人頭疼

1 寫在前面

有印象的朋友應(yīng)該記得我之前寫過(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)題就不值得我寫篇文章來(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)境肯定也沒有問(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__ */

主要處理用戶應(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è)真有這么寫的),如果它的頭文件沒有按照標(biāo)準(zhǔn)的 ifndef 的那種寫法來(lái)寫,那么當(dāng)這個(gè)頭文件被一個(gè)C文件直接或間接包含多次的時(shí)候,這個(gè)定義的變量就會(huì)存在多個(gè)副本,這個(gè)時(shí)候就會(huì)報(bào) “multiple definition”。

可是,我仔細(xì)檢查過(guò)user_app.h的頭部寫法,是正確的,不存在這種問(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),是否存在這種情況。

如何打開生成預(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
image-20220330081507885

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ǔ)言教科書,找了一些關(guān)于C語(yǔ)言的枚舉定義的介紹,再學(xué)習(xí)了一下。

果然userapp.h中的那幾個(gè) xxxt 枚舉并不是一種規(guī)范寫法,倒不是說(shuō)不可以這么寫,只是這樣寫之后容易造成干擾,嚴(yán)重的情況下還會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤。

相關(guān)學(xué)習(xí)資料,可以看 參考鏈接 附錄里面的文章。

image-20220330080320875

4.4 GCC的版本差異

那么問(wèn)題來(lái)了,為何同事的編譯環(huán)境沒報(bào)錯(cuò),而我的編譯環(huán)境報(bào)錯(cuò)了呢?

原來(lái),前段時(shí)間我們的SDK因一個(gè)廠商私有庫(kù)比較新,特意升級(jí)了GCC的版本,由 riscv64unkownelfgcc8.3.0 升級(jí)到了 riscv64unkownelfgcc10.2.0,而應(yīng)用那邊還沒來(lái)得及升級(jí)這個(gè)版本。所以才造成了這樣的沖突。

至于為何兩個(gè)版本有差異呢?后面我也會(huì)提到,其實(shí)8.3.0版本對(duì)這種寫法是有報(bào) 警告 的,而10.2.0版本是報(bào) 錯(cuò)誤 的;在我們的編譯環(huán)境中,除編譯器版本不一樣外,其他由構(gòu)建層傳入的所有編譯選項(xiàng)都是一模一樣的。

那么,下面分析下究竟的差異在哪里。

4.4.1 對(duì)比map文件和匯編代碼

如下圖所示:

image-20220330135821105

匯編文件顯示,兩者編譯出來(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)題。

我想起之前寫過(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ì)還沒人這么用!

這個(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è) 有效鏈接,里面描述的情景,基本跟我的差不多。

image-20220330134754159

摘抄里面的一段話,理解下:

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)在線說(shuō)明文檔,摘抄下里面關(guān)于 -fcommon 選項(xiàng) 和 -fno-common 選項(xiàng)的說(shuō)明,大家理解下:

  1. -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 theextern 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 有差別.

image-20220330171409237

左邊是8.3.0版本,它默認(rèn)使能了 -fcommon 這個(gè)參數(shù)就決定了 mcuotat 編譯到 .common 段;從而鏈接的時(shí)候,并不會(huì)報(bào)警告,而僅僅是報(bào)了一個(gè) warning: multiple common of 警告。

而右邊的10.2.0版本沒有 -fcommon,根據(jù)在線說(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ā)起編譯、鏈接等能看得見的步驟里,兩個(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 枚舉類型詳解 所介紹的方法三那樣。

修改后的代碼如下:

#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的寫法糾正了,我跟對(duì)應(yīng)的應(yīng)用開發(fā)的童鞋聊過(guò),他說(shuō)可能就是寫代碼的時(shí)候 偷懶 了點(diǎn),壓根沒寫到這樣的寫法有啥不妥,最最最重要的是 riscv64unkownelf_gcc8.3.0 的默認(rèn)編譯參數(shù),放任了這種有問(wèn)題的寫法(僅僅是編譯警告,而不是編譯錯(cuò)誤),從而沒有在第一時(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ǔ)言寫法,容不得半點(diǎn)偷懶??!

image-20220330072625523

同時(shí),我們?cè)俜治鱿聠?wèn)題修復(fù)之后,map文件里面對(duì)這幾個(gè)定義的變化,以 mcuotat 為例:

image-20220330074002526

如同我們所預(yù)料的,加上typedef之后,這個(gè)mcuotat已經(jīng)是一個(gè)枚舉類型的別名,并不是一個(gè)變量,自然在map文件肯定找不到它,但是原來(lái)的那種寫法能找到的原因是,它那種是定義了一個(gè)全局變量叫 mcuotat。這才是兩者的本質(zhì)區(qū)別。

6 經(jīng)驗(yàn)總結(jié)

  • 嚴(yán)謹(jǐn)地寫好每一行代碼:了解每一行代碼背后的基礎(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ǔ)法,每一種寫法的搭配代表什么含義,理解并應(yīng)用它,很重要。
  • 認(rèn)真對(duì)待每一個(gè)編譯器提示的 編譯警告:保不準(zhǔn)這些警告哪天就把你帶入坑里,使用GCC的-Werror是個(gè)好選擇,把警告當(dāng)錯(cuò)誤處理,有助于你寫出更為嚴(yán)謹(jǐn)?shù)拇a。
  • GCC的默認(rèn)編譯參數(shù):這個(gè)了解非常有必要,不然下次遇到好端端的代碼編譯不過(guò),就沒轍了。

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,日常分享一些開發(fā)筆記和項(xiàng)目實(shí)戰(zhàn),歡迎指正問(wèn)題。

同時(shí)也非常歡迎關(guān)注我的CSDN主頁(yè)和專欄:

【CSDN主頁(yè):架構(gòu)師李肯】

RT-Thread主頁(yè):架構(gòu)師李肯】

【C/C++語(yǔ)言編程專欄】

【GCC專欄】

信息安全專欄】

【RT-Thread開發(fā)筆記】

freeRTOS開發(fā)筆記】

有問(wèn)題的話,可以跟我討論,知無(wú)不答,謝謝大家。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7581

    瀏覽量

    135650
  • GCC
    GCC
    +關(guān)注

    關(guān)注

    0

    文章

    105

    瀏覽量

    24798
  • 編譯
    +關(guān)注

    關(guān)注

    0

    文章

    647

    瀏覽量

    32746
  • definition
    +關(guān)注

    關(guān)注

    0

    文章

    5

    瀏覽量

    6963
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    GCC編譯優(yōu)化系列】前后編譯的兩版本固件bin大小不一樣?

    GCC編譯優(yōu)化系列】前后編譯的兩個(gè)版本固件bin大小不一樣,怎么辦?
    的頭像 發(fā)表于 09-09 09:01 ?4386次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優(yōu)化</b><b class='flag-5'>系列</b>】前后<b class='flag-5'>編譯</b>的兩版本固件bin大小不一樣?

    Linux 下GCC編譯

    一、Linux 下多文件編譯 在上一篇 Linux 下的 C 編程我們知道了 Linux 下的編譯器為 GCC ,以及如何使用 GCC 進(jìn)行編譯
    的頭像 發(fā)表于 09-11 15:18 ?2299次閱讀
    Linux 下<b class='flag-5'>GCC</b>的<b class='flag-5'>編譯</b>

    ESP32C3編譯出現(xiàn)multiple definition of `g_log_level\'的原因?

    如題,一個(gè)原是ESP32的物聯(lián)網(wǎng)例程,在改為ESP32C3后,編譯出現(xiàn)multiple definition of `g_log_level\'不良。 [2/3] Linking CXX
    發(fā)表于 06-19 08:28

    使用gcc編譯優(yōu)化與不優(yōu)化問(wèn)題

    同樣的程序,使用gcc編譯優(yōu)化與不優(yōu)化的結(jié)果不一代碼如下:1. #include 2.3. int main()4. {5.int i = 1;6.7.i
    發(fā)表于 09-27 10:33

    g++ 編譯靜態(tài)庫(kù)文件時(shí)出現(xiàn)multiple definition of `__dso_handle'錯(cuò)誤,請(qǐng)大神指點(diǎn)如何修改。

    g++ 編譯靜態(tài)庫(kù)文件時(shí)出現(xiàn)multiple definition of `__dso_handle'錯(cuò)誤,請(qǐng)大神指點(diǎn)如何修改。 編譯指令:g++-std=c++11 -o water
    發(fā)表于 07-16 10:39

    【原創(chuàng)精選】RT-Thread征文精選技術(shù)文章合集

    默認(rèn)的鏈接腳本【GCC編譯優(yōu)化系列】static與inline的區(qū)別與聯(lián)系【GCC編譯
    發(fā)表于 07-26 14:56

    求助,ESP32C3編譯出現(xiàn)multiple definition of `g_log_level'怎么解決?

    如題,一個(gè)原是ESP32的物聯(lián)網(wǎng)例程,在改為ESP32C3后,編譯出現(xiàn)multiple definition of `g_log_level'不良。[2/3] Linking CXX
    發(fā)表于 02-16 06:15

    AVR系列單片機(jī)GCC免費(fèi)編譯工具

    AVR系列單片機(jī)GCC免費(fèi)編譯工具
    發(fā)表于 04-13 15:23 ?54次下載

    淺談gcc編譯

    3.3 gcc編譯器 GNU CC(簡(jiǎn)稱為gcc)是GNU項(xiàng)目中符合ANSI C標(biāo)準(zhǔn)的編譯系統(tǒng),能夠編譯用C、C++和Object C等語(yǔ)言
    發(fā)表于 10-18 13:48 ?0次下載

    常見gcc編譯警告整理以及解決方法

     GCC有很多的編譯選項(xiàng),警告選項(xiàng);指定頭文件、庫(kù)路徑;優(yōu)化選項(xiàng)。本文針整理一下GCC的警告選項(xiàng)以及gcc
    發(fā)表于 11-14 11:19 ?2.1w次閱讀

    GCC編譯優(yōu)化指南

    (cpp) → 編譯(gcc或g++) → 匯編(as) → 連接(ld) ;括號(hào)中表示每個(gè)階段所使用的程序,它們分別屬于 GCC 和 Binutils 軟件包。顯然的,優(yōu)化應(yīng)當(dāng)從
    發(fā)表于 04-02 14:36 ?486次閱讀

    gcc編譯優(yōu)化系列】如何獲取gcc默認(rèn)的鏈接腳本

    我們都知道在一般的嵌入式開發(fā)中,使用gcc編譯固件的一般流程是,先把所有的.c文件和.s文件編譯成.o文件,然后把所有的.o文件鏈接成一個(gè)elf文件,最后由elf文件導(dǎo)出bin文件。 那么在鏈接成
    的頭像 發(fā)表于 07-11 09:15 ?3205次閱讀

    GCC編譯優(yōu)化系列】實(shí)戰(zhàn)分析C代碼遇到的編譯問(wèn)題及解決思路

    GCC編譯優(yōu)化系列】實(shí)戰(zhàn)分析C工程代碼可能遇到的編譯問(wèn)題及其解決思路
    的頭像 發(fā)表于 07-10 23:15 ?1323次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優(yōu)化</b><b class='flag-5'>系列</b>】實(shí)戰(zhàn)分析C代碼遇到的<b class='flag-5'>編譯</b>問(wèn)題及解決思路

    GCC編譯優(yōu)化系列】-specs=kernel.specs

    GCC編譯優(yōu)化系列GCC編譯鏈接時(shí)候--specs=kernel.specs鏈接屬性究竟是個(gè)
    的頭像 發(fā)表于 07-11 09:25 ?3218次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優(yōu)化</b><b class='flag-5'>系列</b>】-specs=kernel.specs

    如何從GCC源碼學(xué)編譯原理

    本文結(jié)合編譯原理理論和GCC實(shí)踐做了一個(gè)總結(jié),希望能給需要了解編譯原理和底層知識(shí)的同學(xué)一個(gè)更快的學(xué)習(xí)路徑。
    的頭像 發(fā)表于 03-02 16:15 ?2805次閱讀
    如何從<b class='flag-5'>GCC</b>源碼學(xué)<b class='flag-5'>編譯</b>原理