C 可变大小堆栈框架的组装:这些堆栈对齐指令在分配VLA时似乎无用?
我正在阅读《计算机系统:程序员透视图》第三版和C 可变大小堆栈框架的组装:这些堆栈对齐指令在分配VLA时似乎无用?,c,assembly,x86-64,callstack,stack-frame,C,Assembly,X86 64,Callstack,Stack Frame,我正在阅读《计算机系统:程序员透视图》第三版和3.10.5中支持可变大小堆栈框架的汇编,图3.43让我感到困惑 本书的这一部分试图解释如何生成可变大小的堆栈框架,并以C代码及其汇编版本为例 以下是C和汇编代码(本书图3.43): 我不知道第8-10行的用法是什么。为什么不在第7行之后使用movq%rsp,%r8 (a)C代码 long vframe(long n, long idx, long *q) { long i; long *p[n]; p[0] = &i
3.10.5中支持可变大小堆栈框架的汇编,图3.43
让我感到困惑
本书的这一部分试图解释如何生成可变大小的堆栈框架,并以C代码及其汇编版本为例
以下是C和汇编代码(本书图3.43):
我不知道第8-10行的用法是什么。为什么不在第7行之后使用movq%rsp,%r8
(a)C代码
long vframe(long n, long idx, long *q) {
long i;
long *p[n];
p[0] = &i;
for (i = 1; i < n; i++)
p[i] = q;
return *p[idx];
}
vframe:
2: pushq %rbp
3: movq %rsp, %rbp
4: subq $16, %rsp
5: leaq 22(, %rdi, 8), %rax
6: andq $-16, %rax
7: subq %rax, %rsp
8: leaq 7(%rsp), %rax
9: shrq $3, %rax
10: leaq 0(, %rax, 8), %r8
11: movq %r8, %rcx
................................
12: L3:
13: movq %rdx, (%rcx, %rax, 8)
14: addq $1, %rax
15: movq %rax, -8(%rbp)
16: L2:
17: movq -8(%rbp), %rax
18: cmpq %rdi, %rax
19: jl L3
20: leave
21: ret
以下是我的想法:
在第7行之后,%rsp
应该是16的倍数(%rsp
在调用vframe
之前应该是16的倍数,因为堆栈帧对齐。调用vframe
时,%rsp
减去8以保留调用方的返回地址,然后第2行中的pushq
指令再减去%rsp
,第4行中减去16.So在第7行的开头,%rsp
是16的倍数。在第7行,%rsp
被%rax
减去。由于第6行将%rax
设置为16的倍数,第7行的结果是将%rsp
设置为16的倍数,这意味着%rsp
的低位4位都是零。
然后在第8行,%rsp+7
存储在%rax
中,在第9行,%rax
逻辑右移3位,在第10行,%rax*8
存储在%r8
中
在第7行之后,%rsp
的低位4位都是零。在第8行%rsp+7
中,只需将低位3位设为全1,第9行截断这3位,在第10行%rax*8
中,结果左移3位。因此,最终结果应该是原始的%rsp
(第7行的结果)
所以我想知道8-10行是否没用
为什么不在第7行之后使用
movq%rsp,%r8
,并删除原来的第8-10行?我认为一个有用的探索性程序是将生成的代码减少到:
.globl _vframe
_vframe:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
leaq 22(, %rdi, 8), %rax
andq $-16, %rax
subq %rax, %rsp
leaq 7(%rsp), %rax
shrq $3, %rax
leaq 0(, %rax, 8), %r8
mov %r8, %rax
sub %rsp, %rax
leave
ret
请注意,我刚刚删除了一些有用的代码,并返回了%r8和%rsp之间的差异。
然后,一位司机写道:
extern void *vframe(unsigned long n);
#include <stdio.h>
int main(void) {
int i;
for (i = 0; i < (1<<18); i++) {
void *p = vframe(i);
if (p) {
printf("%d %p\n", i, p);
}
}
return 0;
}
extern void*vframe(无符号长n);
#包括
内部主(空){
int i;
对于(i=0;i<(1Hey@NameNull。不太可能让一个人阅读/拥有你提到的那本书。人们不会走极端,比如找到这本书只是为了回答你的问题。因此,最好在这里提供书中可以看到的所有相关信息。希望你已经阅读了链接。@RC0993,你认为遗漏了什么信息g?这个问题对我来说似乎很完整。NameNull,你的分析是正确的。这不是很好的代码。我能想到的唯一一件事是,编译器有一些常用的代码序列,无论是否需要,它都用于对齐,并且没有动力优化可变大小数组,因为它们不常用。@prl我发现,在一个例子中我提到的这本书的实践问题,它在第4行末尾给出了一些特定的%rsp值,然后让我计算以下修改寄存器的值。这些给定的%rsp值并不都是16的倍数。所以我猜这本书没有假设第7行末尾的%rsp是16的倍数。解决方案是第8-10行的目的是将%r8与8的倍数对齐,以便指针数组对齐(%r8是数组的地址),如果%rsp不是16的倍数,这是有意义的。是的,这是无用且奇怪的。我能够使用gcc4.8.5-Og
重新编写本书的asm输出。(-O1
优化了i
在循环中的存储/重新加载,因为p[i]
实际上不能别名到i
,即使它们都很长
)。因此,与其他一些CS:APP示例不同,这不是作者在伪造的GCC输出中编辑并出错的情况。这种疯狂是真实的。您所询问的指令甚至出现在-O2
,甚至出现在当前的GCC9.2中!!是的,很明显没有优化,因为sizeof(长)您的测试调用方在调用之前总是将RSP 16字节对齐。看起来VLA分配代码正在将数组地址对齐到alignof(long*)
,以防RSP甚至没有对齐8。这太疯狂了,当前的GCC仍然这样做。可能这是32位模式遗留下来的样板文件(其中,GCC希望在例如Windows上的4字节对齐堆栈上对int64_t进行8字节对齐),或者从数组元素与alignof()的可能性出发>堆栈对齐?但您的测试证明,考虑到16字节RSP对齐的ABI保证,对于长*
数组来说,这些东西是多余的。我对VLA是codegen回水的看法有点像“aam”在rdtsc之前,它是猜测cpu频率最稳定的来源。是的,它就在那里,但没有人真正关心它的工作情况。