在一些.h頭文件中或者實現代碼中常常會看到一些以__builtin_
開頭的函數聲明或者調用,好比下面的頭文件#include <secure/_string.h>
中的函數定義:git
//這裏的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_
開頭的符號實際上是一些編譯器內置的函數或者編譯優化處理開關等,其做用相似於宏。宏是高級語言用於預編譯時進行替換的源代碼塊,而內置函數則是用於在編譯階段進行替換的機器指令塊。所以編譯器的這些內置函數其實並非真實的函數,而只是一段指令塊,起到編譯時的內聯功能。 github
在一些編譯器中會對一些標準庫的函數實現改用內置函數來代替,能夠起到性能優化的做用。由於執行這些函數調用會在編譯時變爲直接指令塊的執行,而不會產生指令跳轉、堆棧等相關的操做而引發的函數調用開銷(有一些函數直接就有一條對應的機器指令來實現,若是改用普通函數調用勢必性能大打折扣)。不一樣的編譯器對內置函數的支持不盡相同,並且對因而否用內置函數來實現標準庫函數也沒有統一的標準。好比對於GCC來講它所支持的內置函數都在GCC內置函數列表中被定義和聲明,這些內置函數大部分也被LLVM編譯器所支持。編程
本文不會介紹全部的內置函數,而是隻介紹其中幾個特殊的內置函數以及使用方法。熟練使用這些內置函數能夠提高程序的運行性能以及擴展一些編程的模式。數組
這個函數用來判斷兩個變量的類型是否一致,若是一致返回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
{
}
複製代碼
這個函數用來判斷某個表達式是不是一個常量,若是是常量返回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
複製代碼
這個函數用來獲取一個結構體成員在結構中的偏移量。函數的第一個參數是結構體類型,第二個參數是其中的數據成員的名字。bash
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
複製代碼
這個函數返回調用函數的返回地址,參數爲調用返回的層級,從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
來進行特殊的處理。具體原理能夠參考這些函數的實現,由於它們都已經開源。fetch
這個函數返回調用函數執行時棧內存爲其分配的棧幀(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(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);
複製代碼
這個函數的主要做用是進行條件分支預測。 函數主要有兩個參數: 第一個參數是一個布爾表達式、第二個參數代表第一個參數的值爲真值的機率,這個參數只能取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))
複製代碼
這個函數主要用來實現內存數據的預抓取處理。通常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;
}
複製代碼
歡迎你們訪問歐陽大哥2013的github地址