首页 > 解决方案 > 结构在汇编中是如何组织的?

问题描述

我试图弄清楚,编译器如何在每个结构成员之间填充空间。在这个例子中:

    struct s{
        int a,b,c;
    };
    struct s get(int a){
        struct s foo = {.a=a,.b=a+1,.c=a+2};
        return foo;
    }

编译为cc -S a.c

    .file   "a.c"
    .text
    .globl  get
    .type   get, @function
get:
.LFB0:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -36(%rbp)
    movl    -36(%rbp), %eax
    movl    %eax, -24(%rbp)
    movl    -36(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -20(%rbp)
    movl    -36(%rbp), %eax
    addl    $2, %eax
    movl    %eax, -16(%rbp)
    movq    -24(%rbp), %rax
    movq    %rax, -12(%rbp)
    movl    -16(%rbp), %eax
    movl    %eax, -4(%rbp)
    movq    -12(%rbp), %rax
    movl    -4(%rbp), %ecx
    movq    %rcx, %rdx
    popq    %rbp
    ret
.LFE0:
    .size   get, .-get
    .ident  "GCC: (Debian 8.3.0-6) 8.3.0"
    .section    .note.GNU-stack,"",@progbits

不使用优化。问题是为什么将它们-36(%rbp)用作第一个成员“参考”,当它们按顺序排列时

.a == -24(%rbp)
.b == -20(%rbp)
.c == -16(%rbp)

无需为-36(%rbp)此处使用的编译器腾出空间。是故意的(作为房间或编译器使用-36(%rbp)作为第一个成员的“参考”)?

另外,最后,

movq    -24(%rbp), %rax  #take first member
movq    %rax, -12(%rbp)  #place it randomly
movl    -16(%rbp), %eax  #take third member
movl    %eax, -4(%rbp)   #place it randomly

没有意义,它与初始结构不连续,结构的第一个和第三个成员在函数get分配的空间中随机复制。

结构的约定是什么?

标签: cassemblystructx86

解决方案


您观察到的代码是三个不同事物的混杂: a 的实际布局、struct s如何从函数返回结构的 ABI 规范,以及许多编译器在其默认模式下插入的反优化(相当于-O0)以确保简单调试器可以在任何断点处停止时查找和更改变量的值(有关更多信息,请参阅Why does clang generate inefficient asm with -O0 (for this simple floating point sum)?)。

get您可以通过写入参数来消除这些因素中的第二个struct s *,而不是按值返回结构,并通过编译gcc -O2 -S而不是 just来消除第三个因素gcc -S。(也试试-Ogand -O1; 应用的复杂优化-O2也可能令人困惑。)例如:

$ cat test.c
struct s {
  int a,b,c;
};
void get(int a, struct s *s)
{
  s->a = a;
  s->b = a+1;
  s->c = a+2;
}
$ gcc -O2 -S test.c
$ cat test.s
    .file   "test.c"
    .text
    .p2align 4
    .globl  get
    .type   get, @function
get:
.LFB0:
    .cfi_startproc
    leal    1(%rdi), %eax
    movl    %edi, (%rsi)
    addl    $2, %edi
    movl    %eax, 4(%rsi)
    movl    %edi, 8(%rsi)
    ret
    .cfi_endproc
.LFE0:
    .size   get, .-get
    .ident  "GCC: (Debian 9.3.0-13) 9.3.0"
    .section    .note.GNU-stack,"",@progbits

从这个汇编语言中,应该更清楚的a是在偏移量 0 内struct sb在偏移量 4 和c偏移量 8。

结构布局由每个 CPU 架构的“psABI”(特定于处理器的应用程序二进制接口)指定。您可以在https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI阅读 x86 的 psABI 规范。这些还解释了如何从函数返回结构。知道堆栈帧的布局仅部分由 psABI 指定也很重要。实际上,程序集转储中的一些“随机”偏移量是由编译器任意选择的。


推荐阅读