可变大小堆栈帧的组装:这些堆栈对齐指令在分配 VLA 时似乎没用?
Assembly of variable-size stack frame: these stack-alignment instructions seem useless in allocating a VLA?
我正在阅读计算机系统:程序员的视角第 3 版,3.10.5 Supporting Variable-Size Stack Frames, Figure 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];
}
(b) 部分生成的汇编代码
vframe:
2: pushq %rbp
3: movq %rsp, %rbp
4: subq , %rsp
5: leaq 22(, %rdi, 8), %rax
6: andq $-16, %rax
7: subq %rax, %rsp
8: leaq 7(%rsp), %rax
9: shrq , %rax
10: leaq 0(, %rax, 8), %r8
11: movq %r8, %rcx
................................
12: L3:
13: movq %rdx, (%rcx, %rax, 8)
14: addq , %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保存调用者的return地址,然后第2行的pushq
指令又将%rsp
减8,在第 4 行中是 16。因此在第 7 行的开头,%rsp
是 16 的倍数。在第 7 行中,%rsp
减去 %rax
。由于第 6 行使得 %rax
16的倍数,第7行的结果是设置%rsp
16的倍数)表示%rsp
的低4位全为0
然后第8行%rsp+7
存入%rax
,第9行%rax
逻辑右移3位,第10行%rax*8
存入%r8
.
第7行之后,%rsp
的低4位全为0。第 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 , %rsp
leaq 22(, %rdi, 8), %rax
andq $-16, %rax
subq %rax, %rsp
leaq 7(%rsp), %rax
shrq , %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;
}
去看看。他们总是一样的。所以为什么?当遇到给定的构造(var len 数组)时,它可能是标准代码发射。编译器必须维护某些标准,例如可跟踪的调用框架和对齐方式,os 可能只是将此代码作为已知的解决方案发出。可变长度数组通常被认为是语言中的错误;向 C++ 致敬,为 C 添加半工作、半思考的机制;因此编译器实现者可能不会过多关注代表他们生成的代码。
我正在阅读计算机系统:程序员的视角第 3 版,3.10.5 Supporting Variable-Size Stack Frames, Figure 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];
}
(b) 部分生成的汇编代码
vframe:
2: pushq %rbp
3: movq %rsp, %rbp
4: subq , %rsp
5: leaq 22(, %rdi, 8), %rax
6: andq $-16, %rax
7: subq %rax, %rsp
8: leaq 7(%rsp), %rax
9: shrq , %rax
10: leaq 0(, %rax, 8), %r8
11: movq %r8, %rcx
................................
12: L3:
13: movq %rdx, (%rcx, %rax, 8)
14: addq , %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保存调用者的return地址,然后第2行的pushq
指令又将%rsp
减8,在第 4 行中是 16。因此在第 7 行的开头,%rsp
是 16 的倍数。在第 7 行中,%rsp
减去 %rax
。由于第 6 行使得 %rax
16的倍数,第7行的结果是设置%rsp
16的倍数)表示%rsp
的低4位全为0
然后第8行%rsp+7
存入%rax
,第9行%rax
逻辑右移3位,第10行%rax*8
存入%r8
.
第7行之后,%rsp
的低4位全为0。第 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 , %rsp
leaq 22(, %rdi, 8), %rax
andq $-16, %rax
subq %rax, %rsp
leaq 7(%rsp), %rax
shrq , %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;
}
去看看。他们总是一样的。所以为什么?当遇到给定的构造(var len 数组)时,它可能是标准代码发射。编译器必须维护某些标准,例如可跟踪的调用框架和对齐方式,os 可能只是将此代码作为已知的解决方案发出。可变长度数组通常被认为是语言中的错误;向 C++ 致敬,为 C 添加半工作、半思考的机制;因此编译器实现者可能不会过多关注代表他们生成的代码。