TL;DR
第一个问题:它们的大小相同,因为您的程序的instructions 输出大致相同(更多内容见下文)。此外,它们的大小相同,因为您的 ints 的 size(字节数)永远不会改变。
第二个问题: i 变量存储在您的局部变量框架中,该框架位于函数堆栈中。您设置为i 的实际值在文本段的指令(硬编码)中。
GDB 和汇编
我知道您使用的是 Windows,但请考虑这些代码和 Linux 上的输出。我使用了与您发布的完全相同的来源。
对于第一个,i = 12345678,实际的主要功能是这些计算机指令:
(gdb) disass main
Dump of assembler code for function main:
0x00000000004004ed <+0>: push %rbp
0x00000000004004ee <+1>: mov %rsp,%rbp
0x00000000004004f1 <+4>: mov %edi,-0x14(%rbp)
0x00000000004004f4 <+7>: mov %rsi,-0x20(%rbp)
0x00000000004004f8 <+11>:movl $0xbc614e,-0x4(%rbp)
0x00000000004004ff <+18>:mov $0x0,%eax
0x0000000000400504 <+23>:pop %rbp
0x0000000000400505 <+24>:retq
End of assembler dump.
至于其他程序,i = 0,main是:
(gdb) disass main
Dump of assembler code for function main:
0x00000000004004ed <+0>: push %rbp
0x00000000004004ee <+1>: mov %rsp,%rbp
0x00000000004004f1 <+4>: mov %edi,-0x14(%rbp)
0x00000000004004f4 <+7>: mov %rsi,-0x20(%rbp)
0x00000000004004f8 <+11>:movl $0x0,-0x4(%rbp)
0x00000000004004ff <+18>:mov $0x0,%eax
0x0000000000400504 <+23>:pop %rbp
0x0000000000400505 <+24>:retq
End of assembler dump.
两个代码之间的唯一区别是存储在变量中的实际值。让我们通过下面这几行一步一步来(我的电脑是 x86_64,所以如果你的架构不同,说明可能会有所不同)。
操作码
以及main的实际指令(使用objdump):
00000000004004ed <main>:
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 89 7d ec mov %edi,-0x14(%rbp)
4004f4: 48 89 75 e0 mov %rsi,-0x20(%rbp)
4004f8: c7 45 fc 4e 61 bc 00 movl $0xbc614e,-0x4(%rbp)
4004ff: b8 00 00 00 00 mov $0x0,%eax
400504: 5d pop %rbp
400505: c3 retq
400506: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40050d: 00 00 00
要获得实际的字节差,使用objdump -D prog1 > prog1_dump 和objdump -D prog2 > prog2_dump 以及它们diff prog1_dump prog2_dump:
2c2
< draft1: file format elf64-x86-64
---
> draft2: file format elf64-x86-64
51,58c51,58
< 400283: 00 bc f6 06 64 9f ba add %bh,-0x45609bfa(%rsi,%rsi,8)
< 40028a: 01 3b add %edi,(%rbx)
< 40028c: 14 d1 adc $0xd1,%al
< 40028e: 12 cf adc %bh,%cl
< 400290: cd 2e int $0x2e
< 400292: 11 77 5d adc %esi,0x5d(%rdi)
< 400295: 79 fe jns 400295 <_init-0x113>
< 400297: 3b .byte 0x3b
---
> 400283: 00 e8 add %ch,%al
> 400285: f1 icebp
> 400286: 6e outsb %ds:(%rsi),(%dx)
> 400287: 8a f8 mov %al,%bh
> 400289: a8 05 test $0x5,%al
> 40028b: ab stos %eax,%es:(%rdi)
> 40028c: 48 2d 3f e9 e2 b2 sub $0xffffffffb2e2e93f,%rax
> 400292: f7 06 53 df ba af testl $0xafbadf53,(%rsi)
287c287
< 4004f8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
---
> 4004f8: c7 45 fc 4e 61 bc 00 movl $0xbc614e,-0x4(%rbp)
注意地址0x4004f8你的号码在那里,4e 61 bc 00在prog2和00 00 00 00在prog1,两个4字节等于sizeof(int)。字节c7 45 fc 是其余的指令(将一些值移动到rbp 的偏移量中)。另请注意,不同的前两个部分具有相同的字节大小(21)。所以,你去吧,虽然略有不同,但它们的大小是一样的。
逐步完成组装说明
push %rbp; mov %rsp, %rbp:这称为设置Stack Frame,并且是所有 C 函数的标准(除非您告诉 gcc -fomit-frame-pointer)。这使您可以通过固定寄存器访问堆栈和局部变量,在本例中为rbp。
mov %edi, -0x14(%rbp):这会将寄存器edi 的内容移动到我们的局部变量框架中。具体来说,偏移量-0x14
mov %rsi, -0x20(%rbp):这里也一样。但这次它节省了rsi。这是 x86_64 调用约定的一部分(它现在使用寄存器而不是像 x86_32 那样将所有内容压入堆栈),但是我们没有将它们保存在寄存器中,而是通过将内容保存在局部变量框架中来释放寄存器 - 寄存器更快并且是 CPU 实际处理任何东西的唯一方式,所以我们拥有的空闲寄存器越多越好。
注意:edi 是 rsi 寄存器的 4 字节部分,根据 x86_64 调用约定,我们知道 rsi 寄存器用于第一个参数。 main 的第一个参数是 int argc,所以我们使用一个 4 字节的寄存器来存储它是有意义的。 rsi 是第二个参数,实际上是指向字符的指针的地址 (**argv)。因此,在 64 位架构中,这完全适合 64 位寄存器。
<+11>: movl $0xbc614e,-0x4(%rbp):这是实际的行 int i = 12345678 (0xbc614e = 12345678d)。现在,请注意我们“移动”该值的方式与我们存储main 参数的方式非常相似。我们使用偏移量-0x4(%rbp) 将其存储在“局部变量框架”上(这回答了您关于存储位置的问题)。
mov $0x0, %eax; pop %rbp; retq: 再说一遍,清空帧指针并返回(结束程序,因为我们在 main)。
-
请注意,在第二个示例中,唯一的区别是行<+11>: movl $0x0,-0x4(%rbp),它有效地存储值零 - 用 C 语言编写,int i = 0。
因此,通过这些说明,您可以看到两个程序的 main 函数以完全相同的方式转换为汇编,因此它们的大小最终相同。 (假设您以相同的方式编译它们,因为编译器还在二进制文件中放入了许多其他内容,例如数据、库函数等。在 linux 中,您可以使用 objdump -D program 获得完整的反汇编转储。
注意 2:在这些示例中,您看不到计算机如何从 rsp 中减去值以分配堆栈空间,但通常是这样做的。
堆栈表示
两种情况下的堆栈都是这样的(只有i 的值会改变,或者-0x4(%rbp) 的值会改变)
| ~~~ | Higher Memory addresses
| |
+------------------+ <--- Address 0x8(%rbp)
| RETURN ADDRESS |
+------------------+ <--- Address 0x0(%rbp) // instruction push %rbp
| previous rbp |
+------------------+ <--- Address -0x4(%rbp)
| i=0x11223344 |
+------------------+ <---- Address -0x14(%rbp)
| argc |
+------------------+ <---- address -0x20(%rbp)
| argv |
+------------------+
| |
+~~~~~~~~~~~~~~~~~~+ Lower memory addresses
注意 3:堆栈增长的方向取决于您的架构。数据如何写入内存还取决于您的架构。
资源