在一些.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编译器所支持。
本文不会介绍所有的内置函数,而是只介绍其中几个特殊的内置函数以及使用方法。熟练使用这些内置函数可以提升程序的运行性能以及扩展一些编程的模式。
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
{
}
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
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
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
来进行特殊的处理。具体原理可以参考这些函数的实现,因为它们都已经开源。
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);
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(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