具有这个简单的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 -fverbose-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()函数在stack frame中分配大小字节的空间 来电者。
当变量中的大多数是立即数时,如何具有中间表示形式的变量?
在程序逻辑的SSA (Static Single Assignment)内部表示形式中(例如GCC的GIMPLE),每个临时值都有一个单独的名称。我假设没有直接关联的C变量名称时,数字来自自动编号的SSA变量。但是我对GCC内部并不十分熟悉,无法提供更多详细信息。如果您真的很好奇,可以随时亲自浏览GCC源代码。但是我相当有信心,自动编号的SSA变量可以解释这一点,并且很有意义。
数字文字实际上并没有以-fverbose-asm
命名。例如在优化的GCC输出(from Godbolt)中,我们将其视为将args放入寄存器的一部分:
... movl $3, %r9d #, movl $2, %r8d #, xorl %eax, %eax # ...
re:alloca:在将分配大小四舍五入到16的倍数后,它最终用
subq %rax, %rsp
抵消RSP。此舍入保持堆栈对齐。 (请至少尝试自己动手搜索它。当您缺少许多背景知识和概念时,您将无法期望答案能从头开始全面解释所有内容。当您不了解某些事物的细节时,请从头开始。通过搜索已习惯的技术术语。)顺便说一句,从
gcc -O0
开始,这是效率极低的组件!将分配大小四舍五入为16的倍数
似乎使用了x / 16 * 16
而不是x & 0xFFFF...F0
。 (如果您单步调试器,则可以看到div
和imul
的顺序。)我猜内置函数的固定逻辑顺序是出于某种原因而写的,并且在-O0时,GCC并没有通过它进行恒定的传播。但是无论如何,这就是为什么它使用RAX。也许alloca逻辑是用GIMPLE编写的,或者可能是直到经过一些转换后才能扩展的RTL代码。这就可以解释为什么即使它只是一个语句的一部分,它的优化也是如此差。
gcc -O0
对性能非常不利,但是与具有立即操作数的非常便宜的div
相比,64位and
除以16是非常糟糕的。看到乘以2的乘方作为asm中的立即操作数也很奇怪;在正常情况下,编译器会将其优化为移位。要查看不可怕的汇编,请查看启用优化后会发生什么情况,例如在Godbolt上。
另请参见How to remove "noise" from GCC/clang assembly output?。然后,它仅执行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 #
Silly编译器,此时静态地知道%rsp
的对齐方式,不需要(x+15) & -16
。请注意,-16
=0xFFFFFFFFFFFFFFF0
用64位2的补码表示,因此这是一种表达AND掩码以清除一些低位的简便方法。删除alloca并使用普通的本地数组将提供更简单的代码:
# GCC10.1 -O3 with char buf[600] subq $616, %rsp ... movq %rsp, %rdi ... call snprintf #