7

考虑以下程序:

#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 个局部变量(aib,大小分别为 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 标准等)?

  • 如果不是(我认为汇编输出显示),为什么?
  • 如果是,为什么上面没有发生这种情况?
4

5 回答 5

7

编译器可以随意布局局部变量。它甚至不需要使用堆栈。

如果它使用堆栈,它可以以与堆栈上的声明顺序无关的顺序存储局部变量。

编译器是否允许进行这种优化(通过 C 标准等)?

  • 如果是,为什么上面没有发生这种情况?

好吧,这完全是一种优化吗?

这还不清楚。它少用了几个字节,但这并不重要。char但是在某些架构上,如果它是按字对齐存储的,那么读取它可能会更快。因此,将chars 彼此相邻放置将迫使其中一个至少不对齐字并使其阅读速度变慢。

于 2013-04-09T11:28:18.390 回答
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
于 2013-04-09T12:10:07.273 回答
2

如果编译器改变变量的顺序,不是更有效吗?

如果不谈论特定的 CPU、特定的操作系统和特定的编译器,就无法判断。一般来说,编译器会做的最优化。为了以有意义的方式优化代码,您需要对特定系统有深入的了解。

在您的情况下,编译器可能会设置为在这种情况下优化速度。似乎编译器已经决定每个变量的对齐地址给出最有效的代码。在某些系统上,它不仅更快,而且必须在偶数地址上分配,因为某些 CPU 只能处理对齐的访问。

编译器是否允许进行这种优化(通过 C 标准等)?

是的,C 标准甚至不需要分配变量。编译器可以完全自由地以它想要的任何方式处理这个问题,并且不需要记录如何或为什么。它可以将变量分配到任何地方,可以将它们完全优化掉,或者将它们分配在 CPU 寄存器中,或者在堆栈上,或者在你桌子下面的一个小木箱里。

于 2013-04-09T11:43:28.200 回答
0

通常在速度很重要的正常系统中,逐字阅读比逐字阅读要快。与速度增益相比,内存损失被忽略。但是对于内存很重要的系统,例如在为特定目标平台生成可执行文件(非常通用的含义)的不同交叉编译器中,情况可能会完全不同。编译器可以将它们打包在一起,甚至检查它们的生命周期和用途,这取决于减少位宽等。所以基本上它高度依赖于必要性。但总的来说,如果你想"pack"紧紧地使用它们,每个编译器都会为你提供灵活性。您可以查看手册

于 2013-04-09T11:37:22.953 回答
0

具有堆栈缓冲区溢出保护的编译器(/GS对于 Microsoft 的编译器)可以将变量重新排序作为安全功能。例如,如果您的局部变量是一些恒定大小的 char 数组(缓冲区)和函数指针,则可以溢出缓冲区的攻击者也可以覆盖函数指针。因此,局部变量被重新排序,使得缓冲区靠近金丝雀。这样,攻击者就不能(直接)破坏函数指针,并且(希望)被破坏的金丝雀检测到缓冲区溢出。

警告:这些功能并不能防止妥协,它们只会为攻击者设置障碍,但熟练的攻击者通常会找到解决办法。

于 2013-04-09T11:51:17.243 回答