c语言 - 从机器的角度理解汇编程序中的指针



这是我在godbolt编译器上编写的一个基本程序,它很简单:

#include<stdio.h>
void main()
{
int a = 10;
int *p = &a;
printf("%d", *p);
}

编译后的结果我得到:

.LC0:
.string "%d"
main:
push    rbp
mov     rbp, rsp
sub     rsp, 16
mov     DWORD PTR [rbp-12], 10
lea     rax, [rbp-12]
mov     QWORD PTR [rbp-8], rax
mov     rax, QWORD PTR [rbp-8]
mov     eax, DWORD PTR [rax]
mov     esi, eax
mov     edi, OFFSET FLAT:.LC0
mov     eax, 0
call    printf
nop
leave
ret

问题:推rbp,通过制作一个16字节的块来制作堆栈帧,如何从寄存器将值移动到堆栈位置,反之亦然,LEA的工作是如何计算地址,我得到了这一部分。

问题:

lea     rax, [rbp-12]
mov     QWORD PTR [rbp-8], rax
mov     rax, QWORD PTR [rbp-8]
mov     eax, DWORD PTR [rax]

Lea->将rbp-12的地址获取到rax中,然后将作为rbp-12的地址的值移动到rax中,但下一行再次表示,移动到rax,即rbp-8的值。这似乎模棱两可。然后再次将rax的值移动到eax。我不明白这里的工作量有多大。为什么我不能做

lea     rax, [rbp-12]
mov     QWORD PTR [rbp-8], rax
mov     eax, QWORD PTR [rbp-8]

就这样算了?因为在原始行上,rbp-12的地址存储在rax上,然后rax存储到rbp-8。然后rbp-8再次存储到rax中,然后rax再次存储到eax中?难道我们不能直接把rbp-8复制到eax吗?我想不会。但我的问题是为什么?

我知道指针中存在去引用,所以我理解LEA如何帮助获取rbp-12的地址,但在接下来的部分中,它是什么时候从我完全丢失的地址中获取值的。而且,在那之后,我一句也听不懂。

您看到的是非常未优化的代码。以下是我的逐行解读:

.LC0:
.string "%d"                     ; Format string for printf
main:
push    rbp                      ; Save original base pointer
mov     rbp, rsp                 ; Set base pointer to beginning of stack frame
sub     rsp, 16                  ; Allocate space for stack frame
mov     DWORD PTR [rbp-12], 10   ; Initialize variable 'a'
lea     rax, [rbp-12]            ; Load effective address of 'a'
mov     QWORD PTR [rbp-8], rax   ; Store address of 'a' in 'p'
mov     rax, QWORD PTR [rbp-8]   ; Load 'p' into rax (even though it's already there - heh!)
mov     eax, DWORD PTR [rax]     ; Load 32-bit value of '*p' into eax
mov     esi, eax                 ; Load value to print into esi
mov     edi, OFFSET FLAT:.LC0    ; Load format string address into edi
mov     eax, 0                   ; Zero out eax (not sure why -- likely printf call protocol)
call    printf                   ; Make the printf call
nop                              ; No-op (not sure why)
leave                            ; Remove the stack frame
ret                              ; Return

编译器在不进行优化时,会在解析您提供给它们的代码时生成这样的代码。它做了很多不必要的事情,但生成速度更快,使用调试器也更容易。

将其与优化代码(-O2(进行比较:

.LC0:
.string "%d"                   ; Format string for printf
main:
mov     esi, 10                ; Don't need those variables -- just a 10 to pass to printf!
mov     edi, OFFSET FLAT:.LC0  ; Load format string address into edi
xor     eax, eax               ; It's a few cycles faster to xor a register with itself than to load an immediate 0
jmp     printf                 ; Just jmp to printf -- it will handle the return

优化器发现这些变量不是必需的,因此没有创建堆栈框架。除了printf呼叫,什么都没有了!这是作为jmp完成的,因为当printf完成时,这里不需要做任何其他事情。

最新更新