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