内建函数就是编译器内部实现的函数,可直接使用,无需 #include 对应的头文件才能使用。内建函数的函数命名,通常以 __builtin 开头。编译器会尽量使用硬件指令来实现这些内建函数,比如加法后溢出时的条件跳转,携带时的条件跳转等。
● 用来处理变长参数列表;
● 用来处理程序运行异常;
● 程序的编译优化、性能优化;
● 查看函数运行中的底层信息、堆栈信息等;
● 实现C 标准库的常用函数。
下列内置函数可以检查简单的算术运算是否会溢出。
bool __builtin_add_overflow_p( type1 a, type2 b, type3 c)
bool __builtin_sub_overflow_p( type1 a, type2 b, type3 c)
bool __builtin_mul_overflow_p( type1 a, type2 b, type3 c)
这些内置函数类似于 __builtin_add_overflow 、 __builtin_sub_overflow 或 __builtin_mul_overflow ,不同之处在于它们不会将算术运算的结果存储在任何地方并且最后一个参数不是指针,而是具有除枚举或布尔类型之外的整数类型的某些表达式.
内置函数将前两个操作数提升为无限精度带符号类型,并对这些提升的操作数执行加法运算。然后将结果转换为第三个参数的类型。如果强制转换结果等于无限精度结果,则内置函数返回 false ,否则返回 true 。将忽略第三个参数的值,仅评估第三个参数中的副作用,并且不对最后一个参数执行积分参数提升。如果第三个参数是位字段,则用于结果强制转换的类型具有给定位字段的精度和正负号,而不是基础类型的精度和正负号。
类似地,还有这些内置函数:
bool __builtin_add_overflow( type1 a, type2 b, type3 * res)
bool __builtin_sadd_overflow(int a,int b,int * res)
bool __builtin_saddl_overflow(long int a,long int b,long int* res)
bool __builtin_saddll_overflow(long long int a,long long int b,long long int * res)
bool __builtin_uadd_overflow(unsigned int a,unsigned int b,unsigned int* res)
bool __builtin_uaddl_overflow(unsigned long int a,unsigned long int b,unsigned long int* res)
bool __builtin_uaddll_overflow(unsigned long long int a,unsigned long long int b,unsigned long long int* res)
这些内置函数将前两个操作数提升为无限精度带符号类型,并对这些提升的操作数执行加法运算。然后将结果强制转换为第三个指针参数指向的类型并存储在那里。如果存储的结果等于无限精度的结果,则内置函数返回 false ,否则返回 true 。由于加法运算以无穷大的符号精度执行,因此这些内置函数对所有参数值具有完全定义的行为。
第一个内置函数允许操作数的任意积分类型,结果类型必须是指向枚举型或布尔型以外的某个积分类型的指针,其余的内置函数都有明确的整数类型。
bool __builtin_sub_overflow( type1 a, type2 b, type3 * res)
bool __builtin_ssub_overflow(int a,int b,int * res)
bool __builtin_ssubl_overflow(long int a,long int b,long int* res)
bool __builtin_ssubll_overflow(long long int a,long long int b,long long int* res)
bool __builtin_usub_overflow(unsigned int a,unsigned int b,unsigned int* res)
bool __builtin_usubl_overflow(unsigned long int a,unsigned long int b,unsigned long int* res)
bool __builtin_usubll_overflow(unsigned long int a,unsigned long int b,unsigned long int* res)
这些内置函数与上面的加法溢出检查内置函数类似,只是它们执行的是减法,从第一个参数中减去第二个参数,而不是加法。
bool __builtin_mul_overflow( type1 a, type2 b, type3 * res)
bool __builtin_smul_overflow(int a,int b,int* res)
bool __builtin_smull_overflow(long int a,long int b,long int* res)
bool __builtin_smulll_overflow(long long int a,long long int b,long long int* res)
bool __builtin_umul_overflow(unsigned int a,unsigned int b,unsigned int* res)
bool __builtin_umull_overflow(unsigned long int a,unsigned long int b,unsigned long int* res)
bool __builtin_umulll_overflow(unsigned long long int a,unsigned long long int b,unsigned long long int * res)
这些内置函数与上面的加法溢出检查内置函数类似,只是它们执行的是乘法,而不是加法。
__builtin_return_address(LEVEL);
该函数用来返回当前函数或调用者的返回地址。函数参数LEVEL表示函数在调用链中不同层级的函数。
0:获取当前函数的返回地址
1:获取上一级函数的返回地址
2:获取上二级函数的返回地址
......
__builtin_frame_address(LEVEL);
该函数用来查看函数的栈帧地址。
0:查看当前函数的栈帧地址
1:查看上一级函数的栈帧地址
......
不想使用C标准库函数时,可以加一个前缀__builtin,直接使用对应的内建函数。
常见的C标准库函数如下:
与内存相关的函数:memcpy()、memset()、memcpy()。
数学函数:log()、cos()、abs()、exp()。
字符串处理函数:strcat()、strcmp()、strcpy()、strlen()。
打印函数:printf()、scanf()、putchar()、puts()。
该函数主要用来判断参数n在编译时是否为常量。如果是常量,则函数返回1,否则函数返回0。该函数常用于宏定义中,用来编译优化。
该函数有2个参数,返回值就是其中一个参数,仍是exp。该函数主要用来告诉编译器:参数exp的值为c的可能性很大,然后编译器可以根据这个提示信息,做一些分支预测上的代码优化。
参数c与这个函数的返回值无关,无论c为何值,函数的返回值都是exp。
例如:
int main(void)
{
int a;
a = __builtin_expext(3, 1);
printf("a = %d\n", a);
int a;
a = __builtin_expext(3, 10);
printf("a = %d\n", a);
int a;
a = __builtin_expext(3, 100);
printf("a = %d\n", a);
}
程序运行结果如下:
a = 3
a = 3
a = 3
在Linux内核,我们使用__builtin_expect()内建函数,定义了两个宏。
#define likely(x) __builtin_expext(!!(x), 1)
#define unlikely(x) __builtin_expext(!!(x), 0)
这两个宏的主要作用就是告诉编译器:某个分支发生的概率很高,或者很低,基本不可能发生。编译器根据这个提示信息,在编译程序时就会做一些分支预测上的优化。
在这里对宏的参数x做两次取非操作,就是为了将参数x转换为布尔类型,然后直接与1和0作比较,告诉编译器x为真或为假的可能性很高。
GCC - 6.56 内建函数执行带有溢出检查的算术操作 以下内置函数允许执行简单的算术运算,同时检查是否溢出。 (runebook.dev)
嵌入式C语言自我修养 11:有一种函数,叫内建函数 - 知乎 (zhihu.com)