Given that you didn't specify any optimization flag and used -m32, GCC performed no optimization on your code. The -m32 flag specifies the generation of a 32 bit code for a compiler configured to generate 64 bit code by default. In 32 bit mode, even with optimizations activated, GCC will generate a sub-optimal code given that the only way to do floating point computations in 32 bit mode on Intel machines is through x87 instructions. If you remove the -m32 flag and add -O3 (third level of optimization in GCC) you'll obtain the following assembly code (quite similar to the one generated by Microsoft's CL) :
.LC1:
.string "%d %lf\n"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $1, %esi
movl $.LC1, %edi
movsd .LC0(%rip), %xmm0
movl $1, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC0:
.long 858993459
.long 1072902963
Note : I added a printf to the code because if the GCC optimization pass sees no use of the two variables, they will be removed (dead code elimination). I invite you to check out my post on the subject of optimized vs. non optimized code (Assembly Code - GCC optimized vs not).
You can also notice that CL used an XMM register to store the 64 bit double element stored in .LC0. XMM registers are part of the SSE (Streaming SIMD Extensions) instruction set used mainly for floating point scalar & vector operations. Its implementation is much cleaner and faster than the x87 instruction set.
Q1 :
lea ecx, [esp+4] //load the content of [esp + 4] into ecx
and esp, -8 //align the stack pointer to 8 bytes (same as esp & ~7)
push DWORD PTR [ecx-4] //push the content of [ecx - 4] on the stack
[ecx - 4] = [[esp + 4] - 4]
Let's suppose the stack is in this state :
| main |
| return |
| address @ |
+-----------------+ <--- esp + 4 ---> ecx
| some value |
+-----------------+ <--- esp = ebp
The first instruction puts the existing stack content (main return address @) in ecx. It is equivalent to this :
mov ecx, esp
sub ecx, 4
mov ecx, [ecx]
You can see that the lea instruction does in one take what these instructions do in three takes.
The second instruction aligns esp on an 8 byte boundary. What that means is that the lower 3 order bits of the address pointed by esp will be 0. Memory accesses are faster on Intel machines when aligned on a power of 2 boundary.
The third instruction changes the state of the stack to the following :
| @ |
+-----------------+ <--- esp + 4 ---> ecx
| some value |
+-----------------+ <--- ebp
| @ - 4 |
+-----------------+ <--- esp
Therefore, when the main function is done, it will return to @ - 4.
Q2 :
Let's reason mathematically :
We have : EBP = ESP0
push ecx implies ESP1 = ESP0 - 4
then : ESP2 = ESP1 - 20
therefore : ESP0 = ESP2 - 24
mov DWORD PTR [ebp-12], 1 implies x = EBP - 12 = ESP0 - 12
We know that ESP0 = ESP2 - 24
Therefore x = ESP2 - 24 - 12 = ESP2 - 36
fstp QWORD PTR [ebp-24] implies y = EBP - 24 = ESP0 - 24
Therefore y = ESP2 - 24 - 24 = ESP2 - 48
Now, from this demonstration we extracted the location of the integer x = ESP2 - 36, and the location of the double y = ESP2 - 48.
To compute the distance between both variables, we subtract y from x and obtain the following : x - y = ESP2 - 36 - ESP2 + 48 = 48 - 36 = 12. And that's the amount of bytes used by GCC for storing both of your 32 bit/4 byte and 64 bit/8 byte variables.