在一些.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地址