拥有这个简单的c:
#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
int main(){
char *buf = alloca(600);
snprintf(buf,600,"hi!, %d, %d, %d\n", 1,2,3);
puts(buf);
}
在$cc-S-fverose-asm a. c
:
上生成。
text
.section .rodata
.LC0:
.string "hi!, %d, %d, %d\n"
.text
.globl main
.type main, @function
main:
pushq %rbp #
movq %rsp, %rbp #,
subq $16, %rsp #,
# a.c:7: char *buf = alloca(600);
movl $16, %eax #, tmp102
subq $1, %rax #, tmp89
addq $608, %rax #, tmp90
movl $16, %ecx #, tmp103
movl $0, %edx #, tmp93
divq %rcx # tmp103
imulq $16, %rax, %rax #, tmp92, tmp94
subq %rax, %rsp # tmp94,
movq %rsp, %rax #, tmp95
addq $15, %rax #, tmp96
shrq $4, %rax #, tmp97
salq $4, %rax #, tmp98
movq %rax, -8(%rbp) # tmp98, buf
# a.c:8: snprintf(buf,600,"hi!, %d, %d, %d\n", 1,2,3);
...
gcc决定对这些临时变量进行编号?(tmp102、tmp89、tmp90、…)?
另外,有人能解释一下,为什么alloca
使用%rax
(addq 608美元,%rax
)来分配内存,而不是%rsp
(subq 608美元,%rsp
)?这就是alloca
的用途(根据手册页):alloca()函数在调用者的堆栈框架中分配大小字节的空间。
当大多数变量是直接的时,如何让变量具有中间表示?
在程序逻辑的SSA(静态单一赋值)内部表示(如GCC的GIMPLE)中,每个临时值都有一个单独的名称。当没有直接关联的C变量名时,我会假设这些数字来自自动编号的SSA变量。但是我对GCC内部结构不够熟悉,无法提供更多细节。如果你真的很好奇,你可以自己查看GCC源代码。但是我相当有信心自动编号的SSA变量解释了这一点,并且完全有意义。
数字字面量实际上并没有得到任何名称,例如在优化的GCC输出中(来自戈德博尔特),我们认为这是将参数放入寄存器的一部分:
...
movl $3, %r9d #,
movl $2, %r8d #,
xorl %eax, %eax #
...
re: alloca:在将分配大小四舍五入到16的倍数后,它最终会使用subq%rax,%rsp
抵消RSP。
这种舍入保持了堆栈对齐。(请至少尝试自己谷歌一下。当你缺少很多背景知识和概念时,你不能指望答案从头开始完全解释一切。当你不理解某件事的细节时,从搜索被使用的技术术语开始。)
BTW,gcc-O0
的asm非常低效!它似乎使用x/16*16
而不是x
我猜内置函数的预设逻辑序列是这样写的,出于某种原因,在-O0GCC没有通过它进行恒定传播。但无论如何,这就是它使用RAX的原因。
也许alloca逻辑是用GIMPLE编写的,或者可能RTL代码,直到一些转换通过后才会扩展。这就解释了为什么它优化得如此糟糕,即使它都是单个语句的一部分。gcc-O0
对性能非常不利,但是一个64位的div
除以16是非常糟糕的,相比之下,一个非常便宜的带有立即操作数的和
。在asm中看到乘以2的幂作为立即操作数也是非常奇怪的;在正常情况下,编译器会将其优化为shift。
要查看非可怕的asm,请查看启用优化后会发生什么,例如在Goldbolt上。另请参阅如何从GCC/clang程序集输出中去除“噪音”?。然后它只执行sub 616美元,%rsp
。但是它会在运行时浪费将指针对齐到该空间的指令(以保证空间将是16字节对齐的),即使在此之后RSP的对齐是静态已知的。
# GCC10.1 -O3 -fverbose-asm with alloca
...
subq $616, %rsp # reserve 600 + 16 bytes
leaq 15(%rsp), %r12
andq $-16, %r12 # get a 16-byte aligned pointer into it
movq %r12, %rdi # save the pointer for later instead of recalc before next call
call snprintf #
愚蠢的编译器,此时%rsp
的对齐方式是静态已知的,没有(x 15)
删除alloca并使用普通本地数组提供了更简单的代码:
# GCC10.1 -O3 with char buf[600]
subq $616, %rsp
...
movq %rsp, %rdi
...
call snprintf #