考虑以下计划:
#include <stdio.h>
void some_func(char*, int*, char*);
void stack_alignment(void) {
char a = '-';
int i = 1337;
char b = '+';
some_func(&a, &i, &b); // to prevent the compiler from removing the local variables
printf("%c|%i|%c", a, i, b);
}
它会生成以下程序集(我自己添加的评论,我是程序集的新手):
$ vim stack-alignment.c
$ gcc -c -S -O3 stack-alignment.c
$ cat stack-alignment.s
.file "stack-alignment.c"
.section .rdata,"dr"
LC0:
.ascii "%c|%i|%c\0"
.text
.p2align 2,,3
.globl _stack_alignment
.def _stack_alignment; .scl 2; .type 32; .endef
_stack_alignment:
LFB7:
.cfi_startproc
subl $44, %esp
.cfi_def_cfa_offset 48
movb $45, 26(%esp) // local variable 'a'
movl $1337, 28(%esp) // local variable 'i'
movb $43, 27(%esp) // local variable 'b'
leal 27(%esp), %eax
movl %eax, 8(%esp)
leal 28(%esp), %eax
movl %eax, 4(%esp)
leal 26(%esp), %eax
movl %eax, (%esp)
call _some_func
movsbl 27(%esp), %eax
movl %eax, 12(%esp)
movl 28(%esp), %eax
movl %eax, 8(%esp)
movsbl 26(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
addl $44, %esp
.cfi_def_cfa_offset 4
ret
.cfi_endproc
LFE7:
.def _some_func; .scl 2; .type 32; .endef
.def _printf; .scl 2; .type 32; .endef
如您所见,有3个局部变量(a
,i
和b
),大小为1个字节,4个字节和1个字节。包括填充这将是12字节(假设编译器对齐4个字节)。
如果编译器将变量的顺序更改为(a
,b
,i
),那么内存效率会不会更高?那么只需要8个字节。
这里有一个“图形”表示:
3 bytes unused 3 bytes unused
vvvvvvvvvvv vvvvvvvvvvv
+---+---+---+---+---+---+---+---+---+---+---+---+
| a | | | | i | b | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+
|
v
+---+---+---+---+---+---+---+---+
| a | b | | | i |
+---+---+---+---+---+---+---+---+
^^^^^^^
2 bytes unused
是否允许编译器进行此优化(通过C标准等)?
答案 0 :(得分:7)
编译器可以根据需要自由布局局部变量。它甚至不需要使用堆栈。
如果局部变量使用堆栈,它可以按照与栈上声明顺序无关的顺序存储局部变量。
是否允许编译器进行此优化(通过C标准等)?
- 如果是,为什么不在上面发生?
嗯,这是一个优化吗?
目前尚不清楚。它少了几个字节,但这很少重要。但是在某些体系结构上,如果char
存储字对齐,则读取速度可能会更快。因此,将char
彼此相邻放置将迫使其中一个至少不进行单词对齐并使其读取速度变慢。
答案 1 :(得分:4)
是否允许编译器进行此优化(通过C标准等)?
是
如果是,为什么上面没有发生这种情况?
确实发生了。
仔细阅读汇编程序输出。
movb $45, 26(%esp) // local variable 'a'
movl $1337, 28(%esp) // local variable 'i'
movb $43, 27(%esp) // local variable 'b'
变量a
位于偏移量26处。
变量b
位于偏移量27处。
变量i
位于偏移量28处。
使用你制作的图像现在是:
+---+---+---+---+---+---+---+---+
| | | a | b | i |
+---+---+---+---+---+---+---+---+
^^^^^^^
2 bytes unused
答案 2 :(得分:2)
如果编译器改变变量的顺序
,那么它的内存效率会不会更高
没有谈论特定的CPU,特定的操作系统和特定的编译器,没有办法说出来。通常,编译器可以做到最佳。为了以有意义的方式优化代码,您需要深入了解特定系统。
在您的情况下,在这种情况下,编译器可能会设置为优化速度。似乎编译器已经确定每个变量的对齐地址提供最有效的代码。在某些系统上,它不仅速度更快,而且还必须在偶数地址分配,因为某些CPU只能处理对齐的访问。
是否允许编译器进行此优化(通过C标准等)?
是的,C标准甚至不需要分配变量。编译器可以完全自由地以任何方式处理它,并且不需要记录如何或为什么。它可以将变量分配到任何地方,它可以完全优化它们,或者将它们分配到CPU寄存器内,或堆栈上,或者放在桌面下面的小木盒中。
答案 3 :(得分:0)
通常在速度很重要的正常系统中,单词阅读比阅读字符更快。与速度增益相比的记忆损失被忽略。但是在内存很重要的系统的情况下,例如在为特定目标平台生成可执行(非常通用的意义)的不同交叉编译器中,图像可能完全不同。编译器可以将它们打包在一起,甚至可以检查它们的使用寿命和使用情况,具体取决于减少位宽等等。所以基本上它高度依赖于必要性。但是一般来说,如果你想紧紧地"pack"
它们,每个编译器都会给你灵活性。你可以查看手册
答案 4 :(得分:0)
具有堆栈缓冲区溢出保护的编译器(Microsoft的编译器为/GS
)可以将变量重新排序为安全功能。例如,如果您的局部变量是一些常量大小的char数组(缓冲区)和一个函数指针,那么可以溢出缓冲区的攻击者也可以覆盖函数指针。因此,局部变量被重新排序,使得缓冲区紧邻金丝雀。这样,攻击者就不能(直接)破坏函数指针,并且(希望)被破坏的金丝雀检测到缓冲区溢出。
警告:这些功能不会阻止攻击,它们只是为攻击者增加了障碍,但是熟练的攻击者通常会找到它的方法。