【ARM 嵌入式 C 入门及渐进 3 -- GCC __attribute__ 使用】
文章目錄
- 1.1 __attribute__((weak))
- 1.1.1 弱符號的聲明
- 1.1.2 代碼演示
- 1.1.3 編譯與輸出
- 1.2 extern "C"
- 1.2.1 補充介紹
- 1.2.2 作用場景
- 1.3 `__attribute__((used))`
- 1.4 `__read_mostly`
- 1.5 likely/unlikely
1.1 attribute((weak))
弱符號,這涉及到編譯中符號的概念。在Linux開發環境中,有強符號和弱符號,符號簡單來說就是函數、變量的名字,對于全局(非局部、非static)的函數和變量,能不能重名是有一定規矩的,強、弱符號就是針對這些全局函數和變量來說的。
| 強 | 函數名,賦初值的全局變量 |
| 弱 | 未初始化的全局變量 |
當代碼中同時存在多個強或弱的全局變量時,要遵守如下規則:
- 強符號只能定義一次,否則編譯錯誤
- 強弱符號同時存在,以強符號為準
- 沒有強符號,則從多個弱符號中任選一個,用 –fno-common 編譯選項可以在這種情況下打出 warning。
1.1.1 弱符號的聲明
弱符號的聲明有兩種方式
第一種,用 __attribute__((weak)) 修飾,例如:
第二種,用 #pragma weak 標記,例如:
#pragma weak func1.1.2 代碼演示
main.c 這個文件中 main 函數調用了 2 個 聲明為弱符號的函數,它們是 weak0 和weak1 :
#include <stdio.h>void __attribute__((weak)) weak0(void); void __attribute__((weak)) weak1(void);int main(int argc, char **argv) {/* 嘗試調用弱符號函數 weak0 */if (weak0) {weak0();} else {printf("weak0=%p\n", weak0);}/* 嘗試調用弱符號函數 weak1 */if (weak1) {weak1();} else{printf("weak1=%p\n", weak1);}return 0; }weak.c 這個文件中定義了2個函數(它們還是weak0和weak1),并強制聲明為弱符號:
#include <stdio.h>/* 標記 weak0 為弱符號 */ #pragma weak weak0 /* 標記 weak1 為弱符號 */ void __attribute__((weak)) weak1(void);static char *label = "weak"; void weak0(void) {printf("[%s]%s is called\n", label, __FUNCTION__); } void weak1(void) {printf("[%s]%s is called\n", label, __FUNCTION__); }stong.c 重復定義兩個強函數:void weak0(void) 和 void weak1(void) :
#include <stdio.h>static char *label = "strong"; void weak0(void) {printf("[%s]%s is called\n", label, __FUNCTION__); }void weak1(void) {printf("[%s]%s is called\n", label, __FUNCTION__); }1.1.3 編譯與輸出
編譯 gcc main.c strong.c weak.c
弱符號鏈接成功時,可以被正常調用。
當強符號定義出現時,弱符號定義不起作用,打印出來的是強函數的內容:
1.2 extern “C”
extern “C” 的主要作用就是為了能夠正確實現 C++ 代碼調用其他 C 語言代碼。加上 extern “C” 后,會指示編譯器這部分代碼按C語言(而不是C++)的方式進行編譯。
由于 C++ 支持函數重載,因此編譯器編譯函數的過程中會將函數的參數類型也加到編譯后的代碼中,而不僅僅是函數名。
而 C 語言并不支持函數重載,因此編譯 C 語言代碼的函數時不會帶上函數的參數類型,一般只包括函數名。
1.2.1 補充介紹
由于C、C++ 編譯器對函數的編譯處理是不完全相同的,尤其對于 C++ 來說,支持函數的重載,編譯后的函數一般是以函數名和形參類型來命名的。
例如,函數 void fun(int, int),C++ 編譯后的可能是 _fun_int_int (不同編譯器可能不同,但都采用了類似的機制,用函數名和參數類型來命名編譯后的函數名);而 C 語言沒有類似的重載機制,一般是利用函數名來指明編譯后的函數名的,對應上面的函數可能會是 _fun 這樣的名字。
1.2.2 作用場景
這個功能主要用在下面的情況:
- C++代碼調用C語言代碼;
- 在C++的頭文件中使用;
- 在多個人協同開發時,可能有的人比較擅長C語言,而有的人擅長 C++,這樣的情況下也會有用到。
例如,如果模塊 B 要引用模塊A中定義的全局變量和函數時只需包含模塊A的頭文件即可。這樣模塊 B 中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但并不會報錯;它會在鏈接階段從模塊A編譯生成的目標代碼中找到該函數。
extern 對應的關鍵字是 static,static 表明變量或者函數只能在本模塊中使用,因此,被static修飾的變量或者函數不可能被 extern C 修飾。
1.3 __attribute__((used))
在普通的 C/C++ 程序中,有的時候為了調試,我們會特別地注釋掉某個函數的調用。然而在編譯時,編譯器會發現,代碼中實現了一個函數,但是最終卻沒有調用它,那么為什么還要寫這個函數呢?于是會警告。
__attribute__((used)),表示對于這個函數可能不會調用它、可能用不到它,編譯器不用進行 warning 提示。
而在嵌入式中 中斷函數都是由內部的中斷處理機制通過中斷向量做跳轉調用的,不是開發人員 “顯式” 去調用的,因此在一些規則檢查比較嚴格的編譯器上編譯時,就會出現類似于上面的警告,為了視野干凈我們就添加這個屬性。
向編譯器說明這段代碼有用,即使在沒有用到的情況下編譯器也不會警告!告訴編譯器避免被鏈接器因為未用過而被優化掉。
1.4 __read_mostly
在 arch/arm/kernel/process.c 中有如下定義:
unsigned logn stack_chk_guard __read_mostly參考網上資料了解到 __read_mostly 修飾的變量放在定義為存放在 .data.read_mostly 段中。
#if defined(CONFIG_X86) || defined(CONFIG_SPARC64) #define __read_mostly __attribute__((__section__(".data.read_mostly"))) #else #define __read_mostly #endifLinux 內核被加載時,__read_mostly 修飾的數據將自動被存放到 Cache 中,以提高整個系統的執行效率。
如果所在的平臺 沒有 Cache,或者雖然有Cache,但并不提供存放數據的接口(也就是并不允許人工放置數據在Cache中),這樣定義為 __read_mostly類型的數據將不能存放在Linux內核中,甚至也不能夠被加載到系統內存去執行,將造成Linux 內核啟動失敗。
解決的方法有兩種:
- 修改 include/asm/cache.h 中的 __ready_mostly 定義為:#define __read_mostly
- 修改 arch/xxx/kernel/vmlinux.S,將 .data.read_mostly 段的位置到實際內存空間中去,例如放置在 .data 段之后等等。
1.5 likely/unlikely
在看 linux內核代碼的時候,經常會看到 likely(x) 和 unlikely(x) 宏的使用。那這兩個宏有什么作用呢?
這兩個宏在內核中的定義如下:
# define likely(x) __builtin_expect(!!(x), 1) # define unlikely(x) __builtin_expect(!!(x), 0)可見這里使用了 gcc 的內建函數 __builtin_expect()。
__builtin_expect (long exp, long c) 函數:
該函數用來引導 gcc 進行條件分支預測。在一條指令執行時,由于流水線的作用,CPU可以同時完成下一條指令的取指,這樣可以提高CPU的利用率。在執行條件分支指令時,CPU也會預取下一條執行,但是如果條件分支的結果為跳轉到了其他指令,那 CPU 預取的下一條指令就沒用了,這樣就降低了流水線的效率。
另外,跳轉指令相對于順序執行的指令會多消耗 CPU 時間,如果可以盡可能不執行跳轉,也可以提高 CPU 性能。
使用 __builtin_expect (long exp, long c) 函數可以幫助 gcc 優化程序編譯后的指令序列,使匯編指令盡可能的順序執行,從而提高 CPU 預取指令的正確率和執行效率。
__builtin_expect(exp, c) 接受兩個 long 型的參數,用來告訴 gcc:exp==c 的可能性比較大。
例如,__builtin_expect(exp, 1) 表示程序執行過程中,exp 取到 1 的可能性比較大。該函數的返回值為 exp 自身。
內核中 likely(x) 和 unlikely(x) 宏:
知道 __builtin_expect() 函數的作用之后,我們就知道內核中 likely(x) 和 unlikely(x) 宏的作用了,通過 likely(x) 和 unlikely(x) 宏定義,我們可以得出他們的作用:
- likely(x) 等價于 x,即 if (likely(x)) 等價于 if (x),但是它告訴 gcc,x 取 1 的可能性比較大;
- unlikely(x) 等價于 x,即 if (unlikely(x))等 價于 if (x),但是它告訴 gcc,x 取 0 的可能性比較大。
推薦閱讀:
https://www.cnblogs.com/xiangtingshen/p/10980055.html
https://www.cnblogs.com/tureno/articles/12236495.html
https://bbs.elecfans.com/jishu_1805890_1_1.html
https://blog.csdn.net/jasonchen_gbd/article/details/44968395
總結
以上是生活随笔為你收集整理的【ARM 嵌入式 C 入门及渐进 3 -- GCC __attribute__ 使用】的全部內容,希望文章能夠幫你解決所遇到的問題。