ARM 之十一__weak 和 __attribute__((weak)) 关键字的使用
??今天在使用 Keil (主要是 armcc 編譯器)編譯代碼(華大的 MCU 驅動庫hc32f46x_interrupts.h / c)的時候遇到了有 __weak 關鍵字的函數不起作用的問題,甚是奇怪。之前對于 __weak 關鍵字一直是一個簡單的認知:編譯器自動使用沒有 __weak 的同名函數(如果有的話)替換有 __weak 關鍵字的同名函數,__weak 函數可以沒有定義,且編譯器不會報錯! 至于這個參數詳細的使用細節一直是一知半解,今天借此機會,以 GCC 作為對比,來學習一下 ARM 中的 __weak 關鍵字的具體使用!
來源
??使用過 GCC 以及有 linux 編程經驗的人,對于這個關鍵字應該不陌生。GNU 的編譯器(gcc)擴展了一個關鍵字 __attribute__,通過該關鍵字,用戶可以在聲明時指定特殊的屬性,使用時該關鍵字后跟雙括號內的屬性,例如:__attribute__((屬性名字))。屬性名字都是定義好的,Weak 屬性就是其中之一:__attribute__((weak))。在 linux 源碼中,該關鍵字非常常見:
??GCC 不多介紹,重點關注 ARM。在 ARM 編譯器(armcc)中,支持和 GCC 相同的關鍵字 __attribute__,使用方式也基本相同,如下:
??除此之外,ARM 編譯器(armcc)還擴展了一個關鍵字 __weak,例如:__weak void f(void); 或者 __weak int i;。ARM 的匯編器(armasm)以另一種方式 [WEAK] 支持該特性。
注意:
??在許多源碼中,經常通過宏定義的形式來定義關鍵字,例如 上面linux 中的 __weak 就是 宏定義的 __attribute__((weak))
強/弱符號
??在 GCC 中,被 __attribute__((weak)) 修飾的符號,我們稱之為 弱符號(Weak Symbol)。例如:弱函數、弱變量;沒有 __attribute__((weak)) 修飾的符號被稱為強符號。在 ARM 中,沒有弱符號和強符號這種叫法,只有個弱引用(Weak References) 和 非弱引用(non-weak reference ) 、 弱定義(Weak definitions) 和 非弱定義(non-weak definition) 的介紹章節。
非弱引用
??非弱引用就是我們平常使用的對于非弱函數或者弱變量的引用。如果鏈接器無法在到目前為止已加載內容中解析對正常非弱符號的引用問題,則 它會嘗試通過在庫中找到符號 來解決此問題:
- 如果找不到此類引用,則鏈接器將報告錯誤。
- 如果解析了這樣的引用,則從入口點可以通過至少一個非弱引用來訪問的節區被標記為已使用。這樣可以確保鏈接器不會將該節作為未使用的節刪除。 每個非弱引用都必須通過一個定義來解決。 如果有多個定義,則鏈接器將報告錯誤。
弱引用
??引用弱聲明的函數或者變量的引用即為弱引用。 鏈接器不會從庫中加載對象來解析弱引用。僅當由于其他原因在鏡像中包含了定義時,它才能解析弱引用。弱引用不會導致鏈接器將包含定義的節區標記為已使用,因此鏈接器可能會將其標記為未使用而刪除。
__weak
__weak 關鍵字可以應用于函數和變量的聲明以及函數定義。
聲明
??__weak 可以用于函數聲明或者變量的聲明。對于聲明,此存儲類指定一個 extern 對象聲明,即使該對象不存在,對于該聲明的引用也不會導致鏈接器對未解析的引用(找不到定義的引用)當做錯誤來處理。
??如果在當前編譯單元中可以找到 __weak 聲明定義,則會用找到的定義替換 __weak 引用;對于找不到定義 __weak 的聲明(函數或變量,如上圖的 FuncB),編譯器做如下處理:
- 引用被解析為分支連接指令 BL。等效于將被引用的分支為 NOP
- 直接將引用替換為 NOP 指令
注意:必須是在當前編譯單元,不再當前編譯單元的沒有意義(例如 ExtFuncA 在 main.c 中只有__weak 聲明,但是沒有定義)。具體看下圖的測試代碼:
注意:用 __weak 聲明然后不使用 __weak 定義的函數的行為相當于非弱函數。 這與 _attribute__((weak)) 關鍵字不同!
定義
??用 __weak 定義的函數弱輸出其符號。弱定義的函數的行為類似于正常定義的函數,除非將同名的非弱定義的函數鏈接到同一鏡像中。 如果在同一鏡像中同時存在非弱定義函數和弱定義函數,則對該函數的所有調用都會解析為調用非弱函數,否則直接使用弱定義的函數(與上面的若聲明不同)。
??如果可以使用多個弱定義,則除非使用鏈接器選項 --muldefweak,否則鏈接器會生成一條錯誤消息。在這種情況下,鏈接器隨機選擇一個供所有調用來使用。使用方式如下:
注意,函數的聲明一定不能添加 __weak 關鍵字。具體如下圖:
注意:用 __weak 聲明然后不使用 __weak 定義的函數的行為相當于非弱函數。 這與 _attribute__((weak)) 關鍵字不同!
限制
__attribute__((weak))
??__attribute__關鍵字使您可以指定變量或結構字段,函數和類型的特殊屬性(與具體屬性)。該關鍵字的作用與 __weak 的作用基本是一樣的,在使用時有些不同,此外在某些情況下,編譯的處理也有些區別。
聲明
??這個參數是 GUN 編譯器的一個擴展,ARM 編譯器也支持該關鍵字。__attribute__((weak)) 可以聲明弱變量,并且其聲明方式與 __weak 相比更加靈活。除了 __weak 的聲明方式,我們還可以用 extern int Variable_Attributes_weak_1 __attribute__((weak));
??_attribute__((weak)) 可以聲明弱函數,其聲明方式與 __weak 相比更加靈活。除了 __weak 的聲明方式,我們還可以用 extern int Function_Attributes_weak_0 (int b) __attribute__((weak));。
??任何包含了 __attribute__((weak)); 聲明的文件的中的同名函數定義,都將被當做弱函數。如下圖:
開篇提出的問題就是因為上圖所示的這種情況!
??注意:用 __attribute__((weak)) 聲明然后不使用 __attribute__((weak)) 進行定義的函數的行為就像是弱函數。 這與 __weak 關鍵字的用法不同。
在 GNU 模式中需要 extern 限定符。在非 gnu 模式下,編譯器假設如果變量不是 extern,那么它將像對待其他非弱變量一樣對待。
定義
??用 __attribute__((weak)) 定義的函數弱輸出其符號(與 __weak 相同)。其使用方式有以下兩種:
__attribute__((weak)) void FuncA(void) {printf("Weak FuncA!\r\n"); } /* 或者 */ void __attribute__((weak)) FuncA(void) {printf("Weak FuncA!\r\n"); }??注意:用 __attribute__((weak)) 聲明然后不使用 __attribute__((weak)) 進行定義的函數的行為就像是弱函數。 這與 __weak 關鍵字的用法不同。除此之外,沒有啥不同,這里不再多說!
區別
參考
總結
以上是生活随笔為你收集整理的ARM 之十一__weak 和 __attribute__((weak)) 关键字的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Gitee 使用问题记录
- 下一篇: 王爽 16 位汇编语言学习记录