Home > database >  Assembly: Why there is an empty memory on stack?
Assembly: Why there is an empty memory on stack?

Time:04-01

I use online complier wrote a simple c code :

int main()
{
    int a = 4;
    int&& b = 2;
}

and the main function part of assembly code complied by gcc 11.20 shown below

main:

push    rbp
mov     rbp, rsp
mov     DWORD PTR [rbp-4], 4
mov     eax, 2
mov     DWORD PTR [rbp-20], eax
lea     rax, [rbp-20]
mov     QWORD PTR [rbp-16], rax
mov     eax, 0
pop     rbp
ret

I notice that when initializing 'a', the instruction just simply move an immediate operand directly to memory while for r-value reference 'b', it first store the immediate value into register eax,then move it to the memory, and also there is an unused memory bettween [rbp-8] ~ [rbp-4], I think that whatever immediate value,they just exist, so it has to be somewhere or it just simply use signal to iniltialize(my guess), I want to know more about the underlying logic.

So my question is that:

  1. Why does inilization differs?
  2. Why there is an empty 4-bytes unused memory on stack?

CodePudding user response:

Let me address the second question first.

Note that there are actually three objects defined in this function: the int variable a, the reference b (implemented as a pointer), and the unnamed temporary int with a value of 2 that b points to. In unoptimized compilation, each of these objects needs to be stored at some unique location on the stack, and the compiler allocates stack space naively, processing the variables one by one and assigning each one space below the previous. It evidently chooses to handle them in the following order:

  1. The variable a, an int needing 4 bytes. It goes in the first available stack slot, at [rbp-4].

  2. The reference b, stored as a pointer needing 8 bytes. You might think it would go at [rbp-12], but the x86-64 ABI requires that pointers be naturally aligned on 8-byte boundaries. So the compiler moves down another 4 bytes to achieve this alignment, putting b at [rbp-16]. The 4 bytes at [rbp-8] are unused so far.

  3. The temporary int, also needing 4 bytes. The compiler puts it right below the previously placed variable, at [rbp-20]. True, there was space at [rbp-8] that could have been used instead, which would be more efficient; but since you told the compiler not to optimize, it doesn't perform this optimization. It would if you used one of the -O flags.

As to why a is initialized with an immediate store to memory, whereas the temporary is initialized via a register: to really answer this, you'd have to read the details of the GCC source code, and frankly I don't think you'll find that there is anything very interesting behind it. Presumably there are different code paths in the compiler for creating and initializing named variables versus temporaries, and the code for temporaries may happen to be written as two steps.

It may be that for convenience, the programmer chose to create an extra object in the intermediate representation (GIMPLE or RTL), perhaps because it simplifies the compiler code in handling more general cases. They wouldn't take any trouble to avoid this, because they know that later optimization passes will clean it up. But if you have optimization turned off, this doesn't happen and you get actual instructions emitted for this unnecessary transfer.

CodePudding user response:

In

 int a = 4;

you declare a (typically) 4-byte variable and ask the compiler to fill it with the bit representation of 4. In

int&& b = 2;

you declare a reference ("r-value reference") to, well, to what? To a literal? Is it possible? In C references are typically translated, on the assembly level, into pointers. So one can expect that b will be "a pointer in disguise", that is, without the * and -> semantics. But it will likely occupy 64 bits on a 64-bit machine. Now, pointers must point to some memory stored in RAM, not in registers, cache(s) etc. So the compiler most likely creates a temporary (unnamed) integer, initializes it with 2, and then binds its address to b. I write "most likely" because I doubt the standard standardizes this in such great detail. What we know for sure is that there is an extra unnamed variable involved in the initialization of b in int&& b = 2;.

As for the assembler, I have too little knowledge of it to dare explain anything to you. I guess, however, that the concept of a temporary variable and a pointer behind the && reference solves all your problems here.

  • Related