GCC为可变长数组分配栈内存时的二次对齐操作相关疑问
GCC可变长数组内存分配对齐疑问
问题描述
我有一个关于GCC为可变长数组分配内存的问题,现有C代码如下:
long vframe(long idx, long n, long *q) { long i = 1; long *p[n]; p[0] = &i; for (i = 1; i < n; i++) { p[i] = q; } return *p[idx]; }
我通过gcc编译、objdump反汇编得到对应的汇编代码如下:
0000000000001139 <vframe>: 1139: 55 push %rbp 113a: 48 89 e5 mov %rsp,%rbp 113d: 53 push %rbx 113e: 48 83 ec 48 sub $0x48,%rsp 1142: 48 89 7d c8 mov %rdi,-0x38(%rbp) 1146: 48 89 75 c0 mov %rsi,-0x40(%rbp) 114a: 48 89 55 b8 mov %rdx,-0x48(%rbp) 114e: 48 89 e0 mov %rsp,%rax 1151: 48 89 c6 mov %rax,%rsi 1154: 48 c7 45 d8 01 00 00 movq $0x1,-0x28(%rbp) 115b: 00 115c: 48 8b 45 c0 mov -0x40(%rbp),%rax 1160: 48 8d 50 ff lea -0x1(%rax),%rdx 1164: 48 89 55 e8 mov %rdx,-0x18(%rbp) 1168: 48 89 c2 mov %rax,%rdx 116b: 49 89 d0 mov %rdx,%r8 116e: 41 b9 00 00 00 00 mov $0x0,%r9d 1174: 48 89 c2 mov %rax,%rdx 1177: 48 89 d1 mov %rdx,%rcx 117a: bb 00 00 00 00 mov $0x0,%ebx 117f: 48 8d 14 c5 00 00 00 lea 0x0(,%rax,8),%rdx 1186: 00 1187: b8 10 00 00 00 mov $0x10,%eax 118c: 48 83 e8 01 sub $0x1,%rax 1190: 48 01 d0 add %rdx,%rax 1193: bb 10 00 00 00 mov $0x10,%ebx 1198: ba 00 00 00 00 mov $0x0,%edx 119d: 48 f7 f3 div %rbx 11a0: 48 6b c0 10 imul $0x10,%rax,%rax 11a4: 48 29 c4 sub %rax,%rsp 11a7: 48 89 e0 mov %rsp,%rax 11aa: 48 83 c0 07 add $0x7,%rax 11ae: 48 c1 e8 03 shr $0x3,%rax 11b2: 48 c1 e0 03 shl $0x3,%rax 11b6: 48 89 45 e0 mov %rax,-0x20(%rbp) 11ba: 48 8b 45 e0 mov -0x20(%rbp),%rax 11be: 48 8d 55 d8 lea -0x28(%rbp),%rdx 11c2: 48 89 10 mov %rdx,(%rax) 11c5: 48 c7 45 d8 01 00 00 movq $0x1,-0x28(%rbp) 11cc: 00 11cd: eb 1c jmp 11eb <vframe+0xb2> 11cf: 48 8b 55 d8 mov -0x28(%rbp),%rdx 11d3: 48 8b 45 e0 mov -0x20(%rbp),%rax 11d7: 48 8b 4d b8 mov -0x48(%rbp),%rcx 11db: 48 89 0c d0 mov %rcx,(%rax,%rdx,8) 11df: 48 8b 45 d8 mov -0x28(%rbp),%rax 11e3: 48 83 c0 01 add $0x1,%rax 11e7: 48 89 45 d8 mov %rax,-0x28(%rbp) 11eb: 48 8b 45 d8 mov -0x28(%rbp),%rax 11ef: 48 39 45 c0 cmp %rax,-0x40(%rbp) 11f3: 7f da jg 11cf <vframe+0x96> 11f5: 48 8b 45 e0 mov -0x20(%rbp),%rax 11f9: 48 8b 55 c8 mov -0x38(%rbp),%rdx 11fd: 48 8b 04 d0 mov (%rax,%rdx,8),%rax 1201: 48 8b 00 mov (%rax),%rax 1204: 48 89 f4 mov %rsi,%rsp 1207: 48 8b 5d f8 mov -0x8(%rbp),%rbx 120b: c9 leave 120c: c3 ret
我对以下代码片段存在疑问:
11a4: 48 29 c4 sub %rax,%rsp 11a7: 48 89 e0 mov %rsp,%rax 11aa: 48 83 c0 07 add $0x7,%rax 11ae: 48 c1 e8 03 shr $0x3,%rax 11b2: 48 c1 e0 03 shl $0x3,%rax
前文已通过16字节对齐计算让rsp减去了16的整数倍偏移,为什么还需要对此时存放rsp值的rax执行低3位清零的8字节对齐操作?这是否意味着rsp可能出现不是8的倍数的情况?如果存在该情况,具体会在什么场景下发生?
问题解答
这部分对齐操作是GCC处理可变长数组(VLA)的通用分配逻辑,不是针对当前代码场景的特化实现,具体原因如下:
- 前文的16字节对齐计算确实保证了
sub %rax, %rsp执行后,当前栈指针rsp是16字节对齐的,天然满足8字节对齐要求,当前场景下后续的低3位清零操作看起来是冗余的。 - 这几行代码对齐的不是栈指针
rsp本身,而是最终要存入-0x20(%rbp)的可变长数组p的首地址:GCC的VLA分配模板默认会保证数组首地址匹配数组元素的自然对齐要求,你代码中p的元素是long*类型,64位指针要求8字节自然对齐,所以用低3位清零的操作做8字节对齐。 - 这种通用逻辑的设计是为了兼容所有可能的编译场景,不需要在编译期额外判断当前栈的对齐状态,以下场景就会出现分配VLA前栈对齐不符合预期的情况:
- 使用特殊编译参数修改栈对齐要求时,比如设置
-mpreferred-stack-boundary=3将默认栈对齐改为8字节,就可能出现分配VLA前rsp不是16字节对齐的情况 - 函数内存在手动修改栈指针的内联汇编代码时,也可能导致
rsp对齐不符合预期 - 当VLA的元素类型对齐要求更高时(比如16字节对齐的SSE向量类型),GCC会自动调整对齐掩码,通用逻辑不需要修改就能适配
- 使用特殊编译参数修改栈对齐要求时,比如设置
这部分冗余操作是编译器为了兼容性保留的通用处理,不会影响运行效率,也不代表当前代码存在栈对齐问题。
内容的提问来源于stack exchange,提问作者marcocr xu
相关产品推荐
相关产品推荐

