1

I am trying to figure out, how does compiler pad space between each struct members. In this example:

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

is compiled with 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

No optimization is used. The question is why is there -36(%rbp) used as first member "reference", when they are arranged sequentially in

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

There is no need to make room with -36(%rbp) which compiler uses here. Is it intentionally (as a room or compiler uses the -36(%rbp) as a "reference" to the first member)?

Also, at the end,

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

Does not make sense, it is not sequential with the initial struct, and the first and third member of the struct are copied randomly in the space the function get had allocated.

What is the convention for structs?

milanHrabos
  • 2,010
  • 3
  • 11
  • 45

1 Answers1

3

The code you observe is a jumble of three different things: the actual layout of a struct s, the ABI specification of how to return structs from functions, and the anti-optimizations inserted by many compilers in their default mode (equivalent to -O0) to ensure that unsophisticated debuggers can find and change the values of variables while stopped at any breakpoint (see Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? for more about this).

You can cut out the second of these factors by having get write into a struct s * argument, instead of returning a struct by value, and the third by compiling with gcc -O2 -S instead of just gcc -S. (Also try -Og and -O1; the complex optimizations applied at -O2 can be confusing, too.) For instance:

$ 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

From this assembly language it should be clearer that a is at offset 0 within struct s, b is at offset 4, and c at offset 8.

Struct layout is specified by the "psABI" (processor-specific application binary interface) for each CPU architecture. You can read the psABI specs for x86 at https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI. These also explain how structs are returned from functions. It's also important to know that the layout of a stack frame is only partially specified by the psABI. Some of the "random" offsets in your assembly dump are, in fact, arbitrarily chosen by the compiler.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Not just so unsophisticated debuggers can *find* C vars in memory; so they can *modify* them between C statements and the program will still run as you'd expect the C abstract machine to behave. And / or `jump` to a new source line within the same function. This is part of why `-O0` also rules out constant-propagation outside of a single expression. canonical Q&A: [Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?](https://stackoverflow.com/q/53366394) – Peter Cordes Jun 18 '20 at 16:05
  • @zwof, psABI is too complex for me yet. Isn't in stackoverflow a question regarding struct convetion in asm?I have not found any yet, but hope there is, since it could be with example as well. – milanHrabos Jun 18 '20 at 16:40
  • 1
    If there is such a post, I don't know about it. Any attempt to summarize the psABI rules for struct layout would either be just as complicated as the psABI itself or it would leave enough out to be misleading. Sorry. – zwol Jun 18 '20 at 17:14
  • @PeterCordes Thanks, I've incorporated your notes. – zwol Jun 18 '20 at 17:21
  • @milanHrabos: the struct layout rules are really not that complicated in simple cases for x86-64 System V. Members are laid out in source order (as required by C), with padding if necessary so that member is aligned to `alignof(T)`. [How do I organize members in a struct to waste the least space on alignment?](https://stackoverflow.com/q/56761591) tries to summarize. The Windows ABI has some other rules for aligning within the struct beyond their `alignof()`. But anyway, if you arrange your struct to not need padding, it's pretty trivial. – Peter Cordes Jun 18 '20 at 17:28