關於C語言BSS段問題以及變數定義的解答

2021-06-22 00:32:31 字數 3651 閱讀 3683

非常感謝作者的這篇文章解決了我的疑問

弱符號與強符號概念

鏈結過程實質上就是把不同目標檔案粘在一起,對不同目標檔案中定義或引用的相同名字進行決議resolve和繫結binding。

符號的分類如下:

鏈結關心的是各種全域性符號。

readelf -s ***.o

所有 bind 這一列為 global 的為 全域性符號。

特殊符號

以上位址均指的是載入後的虛擬位址。

符號修飾和符號簽名

name decoration   name mangling

gcc 編譯選項 "-fleading-underscore" 或 "-fno-leading-underscore" 可以開啟或者關閉在編譯時 c 語言符號前加上下劃線。

c++符號修飾因編譯器不同而區別很大。比如下面一段**:

int func(int);

float func(float);

class c {

int func(int);

class c2 {

int func(int);

namespace n {

int func(int);

class c {

int func(int);

在gcc下編譯,其得到的修飾後的符號名稱為:

函式簽名                              修飾後符號名

int func(int)                          _z4funci

float func(float)                    _z4funcf

int c::func(int)                     _zn1c4funcei

int c::c2::func(int)               _zn1c2c24funcei

int n::func(int)                     _zn1n4funcei

int n::c::func(int)                _zn1n1c4funcei

binutils 工具集中的c++filt 可以用於解析被修飾過的名稱

如: c++filt _zn1n1c4funcei 輸出為 n::c::func(int)

如果是vc編譯上面這段**得到的名稱修飾結果為

函式簽名                              修飾後符號名

int func(int)                          ?func@@yahh@z

float func(float)                    ?func@@yamm@z

int c::func(int)                     ?func@c@@aaehh@z

int c::c2::func(int)              ?func@c2@c@@aaehh@z

int n::func(int)                    ?func@n@@yahh@z

int n::c::func(int)               ?func@c@n@@aaehh@z

微軟提供了乙個api將修飾後的名稱轉換為函式簽名,undecoratesymbolname().

在linux平台上, extern 「c」 的作用就是讓 gcc 編譯 c++檔案時,對c++函式或變數不採用c++的方式來進行名稱修飾。

弱符號與強符號

對於c++來說,弱符號通常**於未初始化的全域性變數

。而預設情況下,編譯器將函式和初始化了的全域性變數作為強符號

(全域性變數是前提,能在程式執行前分配記憶體的才存在強弱符號一說,這樣聯結器才有可能裁決到底選擇哪乙個,要是執行時在棧分配記憶體的變數,在同一作用域內多次定義總是出現錯誤!)

可以通過gcc的 __attribute__((weak)) 來定義任何乙個強符號為弱符號。

不同的目標檔案中不能有同名的強符號,否則不能鏈結在一起。

如果乙個符號在某個目標檔案中是強符號,在其它檔案中都是弱符號,那麼該名稱在鏈結時選擇強符號。

如果乙個符號在所有的目標檔案中都是弱符號,則選擇占用空間(位元組數)最大的乙個。

相應的有 弱引用與強引用的概念。

可以將乙個外部函式申明為弱引用,比如下面的做法:

__attribute__((weakref)) void foo();

int main()

if(foo)  foo();

多個符號定義型別不一致及其處理

不一致有三種情況:

第一種情況,在編譯的時候會提示多重定義錯誤,因為多個同名強符號定義本身就是非法的。

後面兩種情況需要鏈結器(ld)來處理。

編譯器把未初始化的全域性變數作為弱符號處理。比如在某個.o中定義了乙個未初始化的全域性變數 global_uninit_var。此時用 readelf -s檢視該變數會看到:

st_name = "global_uninit_var"

st_value = 4

st_size = 4

st_info = 0x11 stb_global stt_object

st_other = 0

st_shndx = 0xfff2 shn_common

發現這個變數是乙個 shn_common型別。這裡使用的是一種成為 common block的機制,是一種事先宣告臨時使用空間的機制。

如果在另乙個.o檔案也定義了相同名字的 global_uninit_var 變數,且未初始化,型別為佔8個位元組的double,則按照common block的鏈結規則,在最終鏈結後的輸出檔案中,global_uninit_var的大小會以輸入檔案中占用空間最大的那個為準。在上面這個例子中,global_uninit_var最終所佔的空間是8個位元組。

common型別的鏈結規則是針對符號都是弱符號的情況,如果其中有個符號是強符號,其他都是弱符號,則最終輸出結果中的符號所佔空間與強符號相同。如果鏈結過程中有弱符號大於強符號,那麼ld鏈結器會報如下警告:

ld: warning: alignment 4 of symbol `global' in a.o is smaller than 8 in b.o

正是由於未初始化的全域性變數(弱符號)其大小在編譯某個目標檔案時未可知,所以那時無法為其在 .bss 節區分配空間。在鏈結過程中,任何乙個弱符號的最終大小都可以確定了,所以它可以在最終輸出檔案的bss段為其分配空間。所以,從最終的輸出可執行檔案來看,未初始化的全域性變數是放在 .bss 節區的。

(這裡有點問題:

在最終輸出檔案這一步並沒有給bss段分配空間,只是在記錄了bss段所需要的大小,也就是說所謂在可執行檔案中,bss段並不佔據目標檔案的任何空間,如果要說有的話,就是儲存bss段大小的儲存空間)【在c專家程式設計中pag117段一節對此有詳細描述】

gcc 可以使用 -fno-common 使得我們可以不以common 塊機制處理未初始化的全域性變數。這時,該符號就相當於乙個強符號。

int global __attribute__((no_common));

當然,如果程式設計師足夠小心,在宣告全域性變數時記住在該加 「extern」 關鍵字時加上它,很多的弱符號型別不一致問題可避免。

__end__

C 和C關於BSS段的區別

當編譯器遇到如下定義 point global 觀念上point的 trivial construction 和 destruction 都會被產生和呼叫,事實上,這些 trival members 要麼沒被產生,要麼沒被呼叫。在c中,global被視為乙個 臨時性的定義 因為他沒有顯示化的初始化操...

1 4 3重定位 使用C語言清除BSS段

使用c語言清除bss段,我們肯定要獲取bss段的起始位址和結束位址,那麼,c語言要怎麼實現使用lds檔案中的變數abc?答 需要以下兩步,這兩步是重點。在函式中宣告該變數為extern型別 型別不重要,因為我們主要是取值,具體原因後面會說 使用時,要取址,比如,int p abc p的值即為lds中...

C語言中為什麼要清除 bss段

bss段裡的內容 顯示初始化為0或者未顯示初始化的全域性變數 顯示初始化為0或者未顯示初始化的static區域性變數。為什麼要清除.bss段 c語言程式在編譯完成後,初始化為非零的全域性變數存放在.data段,而未初始化或初始化為0的全域性變數存放在.bss段中。在生成的可執行檔案中,只有.data...