内建函数是编译器内部实现的函数,通常以 __builtin
开头。这些函数主要在编译器内部使用,其主要是为编译器服务的。内建函数的主要用途如下。
用来处理变长参数列表;
用来处理程序运行异常;
程序的编译优化、性能优化;
查看函数运行中的底层信息、堆栈信息等;
C标准库函数的内建版本。
C语言函数在调用过程中,x86上会将当前函数的返回地址、寄存器等现场信息保存在堆栈中,然后才会跳到被调用函数中去执行。当被调用函数执行结束后,根据保存在堆栈中的返回地址,就可以直接返回到原来的函数中继续执行。
This function returns the return address of the current function, or of one of its callers. The level argument is number
of frames to scan up the call stack. A value of 0 yields the return address of the current function, a value of 1 yields
the return address of the caller of the current function, and so forth.
When inlining the expected behavior is that the function returns the address of the function that is returned to.
To work around this behavior use the noinline function attribute.
The level argument must be a constant integer.
On some machines it may be impossible to determine the return address of any function other than the current one;
in such cases, or when the top of the stack has been reached, this function returns 0 or a random value. In addition,
__builtin_frame_address may be used to determine if the top of the stack has been reached.
__builtin_return_address(LEVEL)
0:返回当前函数的返回地址;
1:返回当前函数调用者的返回地址;
2:返回当前函数调用者的调用者的返回地址;
...
在函数调用过程中,有一个“栈帧”的概念。函数每调用一次,都会将当前函数的现场(返回地址、寄存器等)保存在栈中,每一层函数调用都会将各自的现场信息都保存在各自的栈中。这个各自的栈也就是当前函数的栈帧。多层函数调用就会有多个栈帧,每个栈帧里会保存上一层栈帧的起始地址,这样各个栈帧就形成了一个调用链。很多调试器、内建函数,都是通过回溯函数栈帧调用链来获取函数底层的各种信息的。比如,返回地址、调用关系等。
ARM 处理器使用 FP
和 SP
这两个寄存器,分别指向当前函数栈帧的起始地址和结束地址。
当函数继续调用或者返回,这两个寄存器的值也会发生变化,它们总是指向当前函数栈帧的起始地址和结束地址。
对于 aarch64
的 APCS 可查看 这篇文章。
// 以 aarch64 为例
+----+ <-- high address
| |
+----+ <- fp // 栈帧起始地址
| |
+----+
| |
+----+
| |
+----+ <- sp // 栈顶
| |
+----+
| |
This function is similar to __builtin_return_address, but it returns the address of the function frame rather than
the return address of the function. Calling __builtin_frame_address with a value of 0 yields the frame address of
the current function, a value of 1 yields the frame address of the caller of the current function, and so forth.
The frame is the area on the stack that holds local variables and saved registers. The frame address is normally the
address of the first word pushed on to the stack by the function. However, the exact definition depends upon the
processor and the calling convention. If the processor has a dedicated frame pointer register, and the function has a
frame, then __builtin_frame_address returns the value of the frame pointer register.
__builtin_frame_address(LEVEL)
0:查看当前函数的栈帧地址
1:查看当前函数调用者的栈帧地址
...
举例如下:
#include
void func(void)
{
int *p;
p = __builtin_frame_address(0);
printf("func frame:%p\n",p);
p = __builtin_frame_address(1);
printf("main frame:%p\n",p);
}
int main(void)
{
int *p;
p = __builtin_frame_address(0);
printf("main frame:%p\n",p);
printf("\n");
func();
return 0;
}
main frame:0x40007ff8a0
func frame:0x40007ff880
main frame:0x40007ff8a0
0000000000400548 <func>:
400548: a9be7bfd stp x29, x30, [sp, #-32]!
40054c: 910003fd mov x29, sp
400550: f9000fbd str x29, [x29, #24]
400554: f00002e0 adrp x0, 45f000 <_nl_unload_domain+0x48>
400558: 91306000 add x0, x0, #0xc18
40055c: f9400fa1 ldr x1, [x29, #24]
400560: 9400185e bl 4066d8 <_IO_printf>
400564: f94003a0 ldr x0, [x29]
400568: f9000fa0 str x0, [x29, #24]
40056c: f00002e0 adrp x0, 45f000 <_nl_unload_domain+0x48>
400570: 9130a000 add x0, x0, #0xc28
400574: f9400fa1 ldr x1, [x29, #24]
400578: 94001858 bl 4066d8 <_IO_printf>
40057c: d503201f nop
400580: a8c27bfd ldp x29, x30, [sp], #32
400584: d65f03c0 ret
该函数主要用来判断参数 n 在编译时是否为常量,是常量的话,函数返回1;否则函数返回0。
该函数常用于宏定义中,用于编译优化。一个宏定义,根据宏的参数是常量还是变量,可能实现的方法不一样。
在 Linux 内核中经常看到这样的宏。
#define _dma_cache_sync(addr, sz, dir) \
do { \
if (__builtin_constant_p(dir)) { \
__inline_dma_cache_sync(addr, sz, dir); \
} else { \
__arc_dma_cache_sync(addr, sz, dir); \
} \
} while (0); \
这个函数有两个参数,返回值就是其中一个参数,仍是 exp
。这个函数的意义主要就是告诉编译器:参数 exp 的值为 c 的可能性很大。然后编译器可能就会根据这个提示信息,做一些分支预测上的代码优化。
#include
int main(void)
{
int a;
a = __builtin_expect(3,1);
printf("a = %d\n",a);
a = __builtin_expect(3,10);
printf("a = %d\n",a);
a = __builtin_expect(3,100);
printf("a = %d\n",a);
return 0;
}
a = 3
a = 3
a = 3
实际上 likely unlikely
用的就是这个内建函数实现的。
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
如 __builtin_memcpy
, __builtin_puts
。
内存相关的函数:memcpy 、memset、memcmp
数学函数:log、cos、abs、exp
字符串处理函数:strcat、strcmp、strcpy、strlen
打印函数:printf、scanf、putchar、puts
https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html ↩︎