什么是built-in 函數(shù)嫌松?
在一些.h頭文件中或者實現(xiàn)代碼中經(jīng)常會看到一些以__builtin_
開頭的函數(shù)聲明或者調(diào)用邻耕,比如下面的頭文件#include <secure/_string.h>
中的函數(shù)定義:
//這里的memcpy函數(shù)的由內(nèi)置函數(shù)__builtin___memcpy_chk來實現(xiàn)糖声。
#if __has_builtin(__builtin___memcpy_chk) || defined(__GNUC__)
#undef memcpy
/* void *memcpy(void *dst, const void *src, size_t n) */
#define memcpy(dest, ...) \
__builtin___memcpy_chk (dest, __VA_ARGS__, __darwin_obsz0 (dest))
#endif
這些__builtin_
開頭的符號其實是一些編譯器內(nèi)置的函數(shù)或者編譯優(yōu)化處理開關(guān)等,其作用類似于宏驾讲。宏是高級語言用于預(yù)編譯時進(jìn)行替換的源代碼塊,而內(nèi)置函數(shù)則是用于在編譯階段進(jìn)行替換的機(jī)器指令塊泼舱。因此編譯器的這些內(nèi)置函數(shù)其實并不是真實的函數(shù),而只是一段指令塊枷莉,起到編譯時的內(nèi)聯(lián)功能娇昙。
在一些編譯器中會對一些標(biāo)準(zhǔn)庫的函數(shù)實現(xiàn)改用內(nèi)置函數(shù)來代替,可以起到性能優(yōu)化的作用笤妙。因為執(zhí)行這些函數(shù)調(diào)用會在編譯時變?yōu)橹苯又噶顗K的執(zhí)行冒掌,而不會產(chǎn)生指令跳轉(zhuǎn)噪裕、堆棧等相關(guān)的操作而引起的函數(shù)調(diào)用開銷(有一些函數(shù)直接就有一條對應(yīng)的機(jī)器指令來實現(xiàn),如果改用普通函數(shù)調(diào)用勢必性能大打折扣)股毫。不同的編譯器對內(nèi)置函數(shù)的支持不盡相同膳音,而且對于是否用內(nèi)置函數(shù)來實現(xiàn)標(biāo)準(zhǔn)庫函數(shù)也沒有統(tǒng)一的標(biāo)準(zhǔn)。比如對于GCC來說它所支持的內(nèi)置函數(shù)都在GCC內(nèi)置函數(shù)列表中被定義和聲明铃诬,這些內(nèi)置函數(shù)大部分也被LLVM編譯器所支持祭陷。
本文不會介紹所有的內(nèi)置函數(shù),而是只介紹其中幾個特殊的內(nèi)置函數(shù)以及使用方法趣席。熟練使用這些內(nèi)置函數(shù)可以提升程序的運(yùn)行性能以及擴(kuò)展一些編程的模式兵志。
- __builtin_types_compatible_p()
這個函數(shù)用來判斷兩個變量的類型是否一致,如果一致返回true否則返回false宣肚。這里的變量會忽略一些修飾關(guān)鍵字想罕,比如const int 和 int 會被認(rèn)為是相同的變量類型《で蓿可以用這個函數(shù)來判斷某個變量是否是特定的類型,還可以用這個函數(shù)來做一些類型檢查相關(guān)的防護(hù)邏輯闸迷。一般這個函數(shù)都和typeof
關(guān)鍵字一起使用嵌纲。
int a, b
long c;
int ret1= __builtin_types_compatible_p(typeof(a), typeof(b)); //true
int ret2 = __builtin_types_compatible_p(typeof(a), typeof(c)); //false
int ret3 = __builtin_types_compatible_p(int , const int); //true
if (__builtin_types_compatible_p(typeof(a), int)) //true
{
}
- __builtin_constant_p()
這個函數(shù)用來判斷某個表達(dá)式是否是一個常量,如果是常量返回true否則返回false腥沽。
int a = 10;
const int b = 10;
int ret1 = __builtin_constant_p(10); //true
int ret2 = __builtin_constant_p(a); //false
int ret3 = __builtin_constant_p(b); //true
- __builtin_offsetof()
這個函數(shù)用來獲取一個結(jié)構(gòu)體成員在結(jié)構(gòu)中的偏移量逮走。函數(shù)的第一個參數(shù)是結(jié)構(gòu)體類型,第二個參數(shù)是其中的數(shù)據(jù)成員的名字今阳。
struct S
{
char m_a;
long m_b;
};
int offset1 = __builtin_offsetof(struct S, m_a); //0
int offset2 = __builtin_offsetof(struct S, m_b); //8
struct S s;
s.m_a = 'a';
s.m_b = 10;
char m_a = *(char*)((char*)&s + offset1); //'a'
long m_b = *(long*)((char*)&s + offset2); // 10
- __builtin_return_address()
這個函數(shù)返回調(diào)用函數(shù)的返回地址师溅,參數(shù)為調(diào)用返回的層級,從0開始盾舌,并且只能是一個常數(shù)墓臭。假如有一個函數(shù)調(diào)用棧為A->B->C->D
。那么在D函數(shù)內(nèi)調(diào)用__builtin_return_address(0)返回的是C函數(shù)調(diào)用D函數(shù)的下一條指令的地址妖谴,如果調(diào)用的是__builtin_return_address(1)則返回B函數(shù)調(diào)用C函數(shù)的下一條指令的地址窿锉,依次類推。這個函數(shù)的一個應(yīng)用場景是被調(diào)用者內(nèi)部可以根據(jù)外部調(diào)用者的不同而進(jìn)行差異化處理膝舅。
//這個例子演示一個函數(shù)foo嗡载。如果是被fout1函數(shù)調(diào)用則返回1,被其他函數(shù)調(diào)用時則返回0仍稀。
#include <dlfcn.h>
extern int foo();
void fout1()
{
printf("ret1 = %d\n", foo()); //ret1 = 1
}
void fout2()
{
printf("ret2 = %d\n", foo()); //ret2= 0
}
int foo()
{
void *retaddr = __builtin_return_address(0); //這個返回地址就是調(diào)用者函數(shù)的某一處的地址洼滚。
//根據(jù)返回地址可以通過dladdr函數(shù)獲取調(diào)用者函數(shù)的信息。
Dl_info dlinfo;
dladdr(retaddr, &dlinfo);
if (dlinfo.dli_saddr == fout1)
return 1;
else
return 0;
}
__builtin_return_address()函數(shù)的另外一個經(jīng)典的應(yīng)用是iOS系統(tǒng)中用ARC進(jìn)行內(nèi)存管理時對返回值是OC對象的函數(shù)和方法的特殊處理技潘。比如一個函數(shù)foo返回一個OC對象時遥巴,系統(tǒng)在編譯時會對返回的對象調(diào)用objc_autoreleaseReturnValue函數(shù)千康,而在調(diào)用foo函數(shù)時則會在編譯時插入如下的三條匯編指令:
//arm64位的指令
bl foo
mov fp, fp //這條指令看似無意義,其實這是一條特殊標(biāo)志指令挪哄。
bl objc_retainAutoreleasedReturnValue
如果考察objc_autoreleaseReturnValue函數(shù)的內(nèi)部實現(xiàn)就會發(fā)現(xiàn)其內(nèi)部用了__builtin_return_address函數(shù)吧秕。objc_autoreleaseReturnValue函數(shù)通過調(diào)用__builtin_return_address(0)返回的地址的內(nèi)容是否是mov fp,fp
來進(jìn)行特殊的處理。具體原理可以參考這些函數(shù)的實現(xiàn)迹炼,因為它們都已經(jīng)開源砸彬。
- __builtin_frame_address()
這個函數(shù)返回調(diào)用函數(shù)執(zhí)行時棧內(nèi)存為其分配的棧幀(stack frame)區(qū)間中的高位地址值。參數(shù)為調(diào)用函數(shù)的層級斯入,從0開始并且只能是一個常數(shù)砂碉。這個函數(shù)可以用來實現(xiàn)防止棧內(nèi)存溢出的棧保護(hù)處理。因為調(diào)用函數(shù)內(nèi)定義的任何的局部變量的地址都必須要小于這個地址值刻两。
void foo(char *buf)
{
void *frameaddr = __builtin_frame_address(0);
//定義棧內(nèi)存變量增蹭,長度為100個字節(jié)。
char local[100];
int buflen = strlen(buf); //獲取傳遞進(jìn)來的緩存字符串的長度磅摹。
if (local + buflen > frameaddr) //進(jìn)行棧內(nèi)存溢出判斷滋迈。
{
ptrinf("可能會出現(xiàn)棧內(nèi)存溢出");
return;
}
strcpy(local, buf);
}
- __builtin_choose_expr()
這個函數(shù)主要用于實現(xiàn)在編譯時進(jìn)行分支判斷和選擇處理,從而可以實現(xiàn)在編譯級別上的函數(shù)重載的能力户誓。函數(shù)的格式為:
__builtin_choose_expr(exp, e1, e2)
其所表達(dá)的意思是判斷表達(dá)式exp的值饼灿,如果值為真則使用e1代碼塊的內(nèi)容,而如果值為假時則使用e2代碼塊的內(nèi)容帝美。這個函數(shù)一般都和__builtin_types_compatible_p函數(shù)一起使用碍彭,將類型判斷作為表達(dá)式參數(shù)。比如下面的代碼:
void fooForInt(int a)
{
printf("int a = %d\n", a);
}
void fooForDouble(double a)
{
printf("double a=%f\n", a);
}
//如果x的數(shù)據(jù)類型是整型則使用fooForInt函數(shù)悼潭,否則使用fooForDouble函數(shù)庇忌。
#define fooFor(x) __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), fooForInt(x), fooForDouble(x))
//根據(jù)傳遞進(jìn)入的參數(shù)類型來決定使用哪個具體的函數(shù)。
fooFor(10);
fooFor(10.0);
- __builtin_expect()
這個函數(shù)的主要作用是進(jìn)行條件分支預(yù)測舰褪。 函數(shù)主要有兩個參數(shù): 第一個參數(shù)是一個布爾表達(dá)式皆疹、第二個參數(shù)表明第一個參數(shù)的值為真值的概率,這個參數(shù)只能取1或者0占拍,當(dāng)取值為1時表示布爾表達(dá)式大部分情況下的值是真值墙基,而取值為0時則表示布爾表達(dá)式大部分情況下的值是假值。函數(shù)的返回就是第一個參數(shù)的表達(dá)式的值刷喜。
在一條指令執(zhí)行時残制,由于流水線的作用,CPU可以完成下一條指令的取指掖疮,這樣可以提高CPU的利用率初茶。在執(zhí)行一條條件分支指令時,CPU也會預(yù)取下一條執(zhí)行,但是如果條件分支跳轉(zhuǎn)到了其他指令恼布,那CPU預(yù)取的下一條指令就沒用了螺戳,這樣就降低了流水線的效率。__builtin_expect 函數(shù)可以優(yōu)化程序編譯后的指令序列折汞,使指令盡可能的順序執(zhí)行倔幼,從而提高CPU預(yù)取指令的正確率。例如:
if (__builtin_expect (x, 0))
foo ();
表示x的值大部分情況下可能為假爽待,因此foo()函數(shù)得到執(zhí)行的機(jī)會比較少损同。這樣編譯器在編譯這段代碼時就不會將foo()函數(shù)的匯編指令緊挨著if條件跳轉(zhuǎn)指令。再例如:
if (__builtin_expect (x, 1))
foo ();
表示x的值大部分情況下可能為真鸟款,因此foo()函數(shù)得到執(zhí)行的機(jī)會比較大膏燃。這樣編譯器在編譯這段代碼時就會將foo()函數(shù)的匯編指令緊挨著if條件跳轉(zhuǎn)指令。
為了簡化函數(shù)的使用何什,iOS系統(tǒng)的兩個宏fastpath和slowpath來實現(xiàn)這種分支優(yōu)化判斷處理组哩。
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
本節(jié)參考自:https://blog.csdn.net/jasonchen_gbd/article/details/44948523
- __builtin_prefetch()
這個函數(shù)主要用來實現(xiàn)內(nèi)存數(shù)據(jù)的預(yù)抓取處理。一般CPU內(nèi)部都會提供幾級高速緩存处渣,在高速緩存中進(jìn)行數(shù)據(jù)存取要比在內(nèi)存中速度快伶贰。因此為了提升性能,可以預(yù)先將某個內(nèi)存地址中的數(shù)據(jù)讀取或?qū)懭氲礁咚倬彺嬷腥ス拚唬@樣當(dāng)真實需要對內(nèi)存地址進(jìn)行存取時實際上是在高速緩存中進(jìn)行黍衙。而__builtin_prefetch函數(shù)就是用來將某個內(nèi)存中的數(shù)據(jù)預(yù)先加載或?qū)懭氲礁咚倬彺嬷腥ァ:瘮?shù)的格式如下:
__builtin_prefetch(addr, rw, locality)
其中addr就是要進(jìn)行預(yù)抓取的內(nèi)存地址悠瞬。 rw是一個可選參數(shù)取值只能取0或者1们豌,0表示未來要預(yù)計對內(nèi)存進(jìn)行讀操作涯捻,而1表示預(yù)計對內(nèi)存進(jìn)行寫操作浅妆。locality 取值必須是常數(shù),也稱為“時間局部性”(temporal locality) 障癌。時間局部性是指凌外,如果程序中某一條指令一旦執(zhí)行,則不久之后該指令可能再被執(zhí)行涛浙;如果某數(shù)據(jù)被訪問康辑,則不久之后該數(shù)據(jù)會被再次訪問。該值的范圍在 0 - 3 之間轿亮。為 0 時表示疮薇,它沒有時間局部性,也就是說我注,要訪問的數(shù)據(jù)或地址被訪問之后的短時間內(nèi)不會再被訪問按咒;為 3 時表示,被訪問的數(shù)據(jù)或地址具有高 時間局部性但骨,也就是說励七,在被訪問不久之后非常有可能再次訪問智袭;對于值 1 和 2,則分別表示具有低 時間局部性 和中等 時間局部性掠抬。該值默認(rèn)為 3 吼野。
一般執(zhí)行數(shù)據(jù)預(yù)抓取的操作都是在地址將要被訪問之前的某個時間進(jìn)行。通過數(shù)據(jù)預(yù)抓取可以有效的提高數(shù)據(jù)的存取訪問速度两波。比如下面的代碼實現(xiàn)對數(shù)組中的所有元素執(zhí)行頻繁的寫之前進(jìn)行預(yù)抓取處理:
//定義一個數(shù)組瞳步,在接下來的時間中需要對數(shù)組進(jìn)行頻繁的寫處理,因此可以將數(shù)組的內(nèi)存地址預(yù)抓取到高速緩存中去雨女。
int arr[10];
for (int i = 0; i < 10; i++)
{
__builtin_prefetch(arr+i, 1, 3);
}
//后面會頻繁的對數(shù)組元素進(jìn)行寫入處理谚攒,因此如果不調(diào)用預(yù)抓取函數(shù)的話,每次寫操作都是直接對內(nèi)存地址進(jìn)行寫處理氛堕。
//而當(dāng)使用了高速緩存后馏臭,這些寫操作可能只是在高速緩存中執(zhí)行。
for (int i = 0; i < 1000000; i++)
{
arr[i%10] = i;
}
本節(jié)參考自:https://blog.csdn.net/chrysanthemumcao/article/details/8907566