LLVM編譯器中的內置(built-in)函數

什么是built-in 函數?

在一些.h頭文件中或者實現代碼中經常會看到一些以__builtin_開頭的函數聲明或者調用,比如下面的頭文件#include <secure/_string.h>中的函數定義:

//這里的memcpy函數的由內置函數__builtin___memcpy_chk來實現。
#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_開頭的符號其實是一些編譯器內置的函數或者編譯優化處理開關等,其作用類似于宏。宏是高級語言用于預編譯時進行替換的源代碼塊,而內置函數則是用于在編譯階段進行替換的機器指令塊。因此編譯器的這些內置函數其實并不是真實的函數,而只是一段指令塊,起到編譯時的內聯功能。

內置函數和非內置函數的調用的區別

在一些編譯器中會對一些標準庫的函數實現改用內置函數來代替,可以起到性能優化的作用。因為執行這些函數調用會在編譯時變為直接指令塊的執行,而不會產生指令跳轉、堆棧等相關的操作而引起的函數調用開銷(有一些函數直接就有一條對應的機器指令來實現,如果改用普通函數調用勢必性能大打折扣)。不同的編譯器對內置函數的支持不盡相同,而且對于是否用內置函數來實現標準庫函數也沒有統一的標準。比如對于GCC來說它所支持的內置函數都在GCC內置函數列表中被定義和聲明,這些內置函數大部分也被LLVM編譯器所支持。

本文不會介紹所有的內置函數,而是只介紹其中幾個特殊的內置函數以及使用方法。熟練使用這些內置函數可以提升程序的運行性能以及擴展一些編程的模式。

  • __builtin_types_compatible_p()
    這個函數用來判斷兩個變量的類型是否一致,如果一致返回true否則返回false。這里的變量會忽略一些修飾關鍵字,比如const int 和 int 會被認為是相同的變量類型。可以用這個函數來判斷某個變量是否是特定的類型,還可以用這個函數來做一些類型檢查相關的防護邏輯。一般這個函數都和typeof關鍵字一起使用。
 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()
    這個函數用來判斷某個表達式是否是一個常量,如果是常量返回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()
    這個函數用來獲取一個結構體成員在結構中的偏移量。函數的第一個參數是結構體類型,第二個參數是其中的數據成員的名字。
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()
    這個函數返回調用函數的返回地址,參數為調用返回的層級,從0開始,并且只能是一個常數。假如有一個函數調用棧為A->B->C->D。那么在D函數內調用__builtin_return_address(0)返回的是C函數調用D函數的下一條指令的地址,如果調用的是__builtin_return_address(1)則返回B函數調用C函數的下一條指令的地址,依次類推。這個函數的一個應用場景是被調用者內部可以根據外部調用者的不同而進行差異化處理。
//這個例子演示一個函數foo。如果是被fout1函數調用則返回1,被其他函數調用時則返回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);  //這個返回地址就是調用者函數的某一處的地址。  
    //根據返回地址可以通過dladdr函數獲取調用者函數的信息。
    Dl_info dlinfo;
    dladdr(retaddr, &dlinfo);
    if (dlinfo.dli_saddr == fout1)
        return 1;
    else
        return 0;
}

__builtin_return_address()函數的另外一個經典的應用是iOS系統中用ARC進行內存管理時對返回值是OC對象的函數和方法的特殊處理。比如一個函數foo返回一個OC對象時,系統在編譯時會對返回的對象調用objc_autoreleaseReturnValue函數,而在調用foo函數時則會在編譯時插入如下的三條匯編指令:

//arm64位的指令
bl foo
mov fp, fp    //這條指令看似無意義,其實這是一條特殊標志指令。
bl objc_retainAutoreleasedReturnValue

如果考察objc_autoreleaseReturnValue函數的內部實現就會發現其內部用了__builtin_return_address函數。objc_autoreleaseReturnValue函數通過調用__builtin_return_address(0)返回的地址的內容是否是mov fp,fp來進行特殊的處理。具體原理可以參考這些函數的實現,因為它們都已經開源。

  • __builtin_frame_address()
    這個函數返回調用函數執行時棧內存為其分配的棧幀(stack frame)區間中的高位地址值。參數為調用函數的層級,從0開始并且只能是一個常數。這個函數可以用來實現防止棧內存溢出的棧保護處理。因為調用函數內定義的任何的局部變量的地址都必須要小于這個地址值。
void foo(char *buf)
{
   void *frameaddr =  __builtin_frame_address(0);
  
   //定義棧內存變量,長度為100個字節。
   char local[100];

   int buflen = strlen(buf);   //獲取傳遞進來的緩存字符串的長度。
   if (local + buflen > frameaddr)  //進行棧內存溢出判斷。
   {
         ptrinf("可能會出現棧內存溢出");
         return;
   }
   
  strcpy(local, buf);
}

  • __builtin_choose_expr()
    這個函數主要用于實現在編譯時進行分支判斷和選擇處理,從而可以實現在編譯級別上的函數重載的能力。函數的格式為:
    __builtin_choose_expr(exp, e1, e2)
    其所表達的意思是判斷表達式exp的值,如果值為真則使用e1代碼塊的內容,而如果值為假時則使用e2代碼塊的內容。這個函數一般都和__builtin_types_compatible_p函數一起使用,將類型判斷作為表達式參數。比如下面的代碼:
void fooForInt(int a)
{
    printf("int a = %d\n", a);
}

void fooForDouble(double a)
{
    printf("double a=%f\n", a);
}

//如果x的數據類型是整型則使用fooForInt函數,否則使用fooForDouble函數。
#define fooFor(x) __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), fooForInt(x), fooForDouble(x))

//根據傳遞進入的參數類型來決定使用哪個具體的函數。
fooFor(10);
fooFor(10.0);

  • __builtin_expect()
    這個函數的主要作用是進行條件分支預測。 函數主要有兩個參數: 第一個參數是一個布爾表達式、第二個參數表明第一個參數的值為真值的概率,這個參數只能取1或者0,當取值為1時表示布爾表達式大部分情況下的值是真值,而取值為0時則表示布爾表達式大部分情況下的值是假值。函數的返回就是第一個參數的表達式的值。
    在一條指令執行時,由于流水線的作用,CPU可以完成下一條指令的取指,這樣可以提高CPU的利用率。在執行一條條件分支指令時,CPU也會預取下一條執行,但是如果條件分支跳轉到了其他指令,那CPU預取的下一條指令就沒用了,這樣就降低了流水線的效率。__builtin_expect 函數可以優化程序編譯后的指令序列,使指令盡可能的順序執行,從而提高CPU預取指令的正確率。例如:
if (__builtin_expect (x, 0))
     foo ();

表示x的值大部分情況下可能為假,因此foo()函數得到執行的機會比較少。這樣編譯器在編譯這段代碼時就不會將foo()函數的匯編指令緊挨著if條件跳轉指令。再例如:

if (__builtin_expect (x, 1))
     foo ();

表示x的值大部分情況下可能為真,因此foo()函數得到執行的機會比較大。這樣編譯器在編譯這段代碼時就會將foo()函數的匯編指令緊挨著if條件跳轉指令。

為了簡化函數的使用,iOS系統的兩個宏fastpath和slowpath來實現這種分支優化判斷處理。


#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

本節參考自:https://blog.csdn.net/jasonchen_gbd/article/details/44948523

  • __builtin_prefetch()
    這個函數主要用來實現內存數據的預抓取處理。一般CPU內部都會提供幾級高速緩存,在高速緩存中進行數據存取要比在內存中速度快。因此為了提升性能,可以預先將某個內存地址中的數據讀取或寫入到高速緩存中去,這樣當真實需要對內存地址進行存取時實際上是在高速緩存中進行。而__builtin_prefetch函數就是用來將某個內存中的數據預先加載或寫入到高速緩存中去。函數的格式如下:
    __builtin_prefetch(addr, rw, locality)
    其中addr就是要進行預抓取的內存地址。 rw是一個可選參數取值只能取0或者1,0表示未來要預計對內存進行讀操作,而1表示預計對內存進行寫操作。locality 取值必須是常數,也稱為“時間局部性”(temporal locality) 。時間局部性是指,如果程序中某一條指令一旦執行,則不久之后該指令可能再被執行;如果某數據被訪問,則不久之后該數據會被再次訪問。該值的范圍在 0 - 3 之間。為 0 時表示,它沒有時間局部性,也就是說,要訪問的數據或地址被訪問之后的短時間內不會再被訪問;為 3 時表示,被訪問的數據或地址具有高 時間局部性,也就是說,在被訪問不久之后非常有可能再次訪問;對于值 1 和 2,則分別表示具有低 時間局部性 和中等 時間局部性。該值默認為 3 。
    一般執行數據預抓取的操作都是在地址將要被訪問之前的某個時間進行。通過數據預抓取可以有效的提高數據的存取訪問速度。比如下面的代碼實現對數組中的所有元素執行頻繁的寫之前進行預抓取處理:
//定義一個數組,在接下來的時間中需要對數組進行頻繁的寫處理,因此可以將數組的內存地址預抓取到高速緩存中去。
int arr[10];
for (int i = 0; i < 10; i++)
{
     __builtin_prefetch(arr+i, 1, 3);
}

//后面會頻繁的對數組元素進行寫入處理,因此如果不調用預抓取函數的話,每次寫操作都是直接對內存地址進行寫處理。
//而當使用了高速緩存后,這些寫操作可能只是在高速緩存中執行。
for (int i = 0; i < 1000000; i++)
{
     arr[i%10] = i;
}

本節參考自:https://blog.csdn.net/chrysanthemumcao/article/details/8907566


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • 創業不等于從0到1,而是從0到N的過程 創新中的從0到1只是完成了萬里長城的第一步。真正的創新,是要走完從0到N的...
    丿卿閱讀 656評論 0 0
  • 晴 大概是晴天?上課的一天,被刑分支配的一天。很難受。 沒什么好說的,依舊是越來越聽不下去,連教室里面的人都越來越...
    Cheryl_ak717閱讀 189評論 0 0
  • 第193章回顧 此時雖然已經是下午,皇宮里還是一日既往的熱鬧,宮女們在各個殿之間穿梭著,皇宮的軍營里也同樣是一片熙...
    陳瀛Neptune閱讀 352評論 27 22
  • 1 我很喜歡一個人旅游,說走就走,說停就停,感覺比人多時更放松、放空。 至于安全,好女子渾身是膽。跑得過色狼,...
    青梨夜閱讀 2,051評論 2 21
  • 又一年的七夕節。于我,更多的不是愛情,而是我滿腦子的親情。 七仙女,金牛姑。 關于七夕,我的記憶版本是:七夕這天,...
    peach桃子閱讀 263評論 0 0