LLVM編譯器中的內(nèi)置(built-in)函數(shù)

什么是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)功能娇昙。

內(nèi)置函數(shù)和非內(nèi)置函數(shù)的調(diào)用的區(qū)別

在一些編譯器中會對一些標(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


歡迎大家訪問歐陽大哥2013的github地址簡書地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讼稚,一起剝皮案震驚了整個濱河市括儒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锐想,老刑警劉巖帮寻,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赠摇,居然都是意外死亡固逗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門藕帜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烫罩,“玉大人,你說我怎么就攤上這事洽故”丛埽” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵时甚,是天一觀的道長隘弊。 經(jīng)常有香客問我,道長荒适,這世上最難降的妖魔是什么梨熙? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮刀诬,結(jié)果婚禮上咽扇,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好肌割,可當(dāng)我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布卧蜓。 她就那樣靜靜地躺著,像睡著了一般把敞。 火紅的嫁衣襯著肌膚如雪弥奸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天奋早,我揣著相機(jī)與錄音盛霎,去河邊找鬼。 笑死耽装,一個胖子當(dāng)著我的面吹牛愤炸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掉奄,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼规个,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姓建?” 一聲冷哼從身側(cè)響起诞仓,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎速兔,沒想到半個月后墅拭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涣狗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年谍婉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镀钓。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡穗熬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掸宛,到底是詐尸還是另有隱情死陆,我是刑警寧澤招拙,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布唧瘾,位于F島的核電站,受9級特大地震影響别凤,放射性物質(zhì)發(fā)生泄漏饰序。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一规哪、第九天 我趴在偏房一處隱蔽的房頂上張望求豫。 院中可真熱鬧,春花似錦、人聲如沸蝠嘉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚤告。三九已至努酸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杜恰,已是汗流浹背获诈。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留心褐,地道東北人舔涎。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像逗爹,于是被迫代替她去往敵國和親亡嫌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 創(chuàng)業(yè)不等于從0到1掘而,而是從0到N的過程 創(chuàng)新中的從0到1只是完成了萬里長城的第一步昼伴。真正的創(chuàng)新,是要走完從0到N的...
    丿卿閱讀 650評論 0 0
  • 晴 大概是晴天镣屹?上課的一天圃郊,被刑分支配的一天。很難受女蜈。 沒什么好說的持舆,依舊是越來越聽不下去,連教室里面的人都越來越...
    Cheryl_ak717閱讀 188評論 0 0
  • 第193章回顧 此時雖然已經(jīng)是下午伪窖,皇宮里還是一日既往的熱鬧逸寓,宮女們在各個殿之間穿梭著,皇宮的軍營里也同樣是一片熙...
    陳瀛Neptune閱讀 351評論 27 22
  • 1 我很喜歡一個人旅游勋篓,說走就走,說停就停魏割,感覺比人多時更放松譬嚣、放空。 至于安全钞它,好女子渾身是膽拜银。跑得過色狼殊鞭,...
    青梨夜閱讀 2,036評論 2 21
  • 又一年的七夕節(jié)。于我尼桶,更多的不是愛情操灿,而是我滿腦子的親情。 七仙女泵督,金牛姑牲尺。 關(guān)于七夕,我的記憶版本是:七夕這天幌蚊,...
    peach桃子閱讀 259評論 0 0