【问题标题】:assembly lea instruction of int *q = p++ and int c = a++int *q = p++ 和 int c = a++ 的汇编 lea 指令
【发布时间】:2017-11-30 02:47:44
【问题描述】:

为了加深对“ (*p)++ ”工作原理的印象,我写了一些测试代码,例如:

int main()
{
  int  a = 3;
  int *p = &a;
  int b = (*p)++;
  int *q = p++;
  int c = a++;
  int d = c++;
  printf("a = %d, b = %d, c = %d, d = %d, p = %#x, q = %#x\n",a, b, c, d, p, q);
}

输出为:a = 5,b = 3,c = 5,d = 4,p = 0xc6dc3490,q = 0xc6dc348c

但我的问题是关于程序集的(代码是按顺序排列的,而不是断断续续的):

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 48

;int a = 3 :
        mov     DWORD PTR [rbp-36], 3

;int *p = &a :
        lea     rax, [rbp-36]
        mov     QWORD PTR [rbp-8], rax

;int b = (*p)++ :
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        lea     ecx, [rax+1]               ;Flag1
        mov     rdx, QWORD PTR [rbp-8]
        mov     DWORD PTR [rdx], ecx
        mov     DWORD PTR [rbp-12], eax

;int *q = p++ :
        mov     rax, QWORD PTR [rbp-8]     ;Flag2
        lea     rdx, [rax+4]               ;Flag3
        mov     QWORD PTR [rbp-8], rdx
        mov     QWORD PTR [rbp-24], rax

;int c = a++;
        mov     eax, DWORD PTR [rbp-36]
        lea     edx, [rax+1]               ;Flag4
        mov     DWORD PTR [rbp-36], edx
        mov     DWORD PTR [rbp-28], eax

;int d = c++;
        mov     eax, DWORD PTR [rbp-28]
        lea     edx, [rax+1]               ;Flag5
        mov     DWORD PTR [rbp-28], edx
        mov     DWORD PTR [rbp-32], eax

... ... (ignore some)

请注意让我感到困惑的“Flagx”行。
从上面我们知道
当指针:int *q = p++ :

lea     rdx, [rax+4]    ;Flag3

这里,'lea' 似乎读取了 'rax' 和 +4 中的 addr 值存储。然后传递给“rdx”。

而:int c = a++int d = c++

lea     edx, [rax+1]    ;Flag4/Flag5

这里,'lea'似乎读取了'rax'(这里是3)中的addr值存储的内容,并且+1,来到4并传递给'edx'。

但是!关键是这两个语句中的“rax”是同一个。他们都来自

mov     rax, QWORD PTR [rbp-8]   ;Flag2

正如我们所看到的,它们(Flag3 和 Flag4/Flag5)看起来非常相似,但是它们基于相同的“rax”工作方式却大不相同,为什么? 'lea' 指令能否区分 'rdx' 和 'edx / ecx' 并得出不同的结果?
非常感谢。

【问题讨论】:

  • 谢谢,但我想我可能没有清楚地描述我的问题。我想知道为什么在 "lea rdx, [rax+4]" 之后,'rdx' 存储 'rax' 值(这意味着一个地址,如:0xeafffac0)+4,在 "lea edx, [rax+1]" 之后, 'edx' 存储 'rax' 地址值的内容(这里是:3,不再是地址)+1。
  • @ProbHunter 请随时edit 澄清您的问题。
  • @Stargateur 谢谢你,我已经对我的帖子做了一些修改,希望这次能让我的问题更清楚。这是我第一次在stackoverflow上发布问题,我的英语不够好,谢谢你的理解..
  • 如果您在启用优化的情况下进行编译,要查看的指令会少很多。所有不必要的负载/存储都消失了。例如int foo(int a) { return a+1; }will compile to lea eax, [rdi+1] / ret。 (或者使用 Windows ABI,其中第一个整数 arg 进入 RCX,lea eax, [rcx+1]。)

标签: c assembly x86


【解决方案1】:

这里,'lea' 似乎读取了 'rax' 中存储的 addr 值的内容(这里是 3),然后 +1,来到 4 并传递给 'edx'。

不,你错了。 lea edx, [rax+1] 不会改变 rax。在评估 lea 指令之前,rax 已经是 3

但是!关键是这两个陈述中的“rax”是同一个。他们都来自mov rax, QWORD PTR [rbp-8]

不,你错了。 rax 正在由 mov eax, DWORD PTR [rbp-36] 设置。

通用寄存器的不同部分可以使用不同的名称来引用。

   64                  32        16    8    0
    |                   |         |    |    |
    v                   v         v    v    v
     +----+----+----+----+----+----+----+----+
     |    |    |    |    |    |    |    |    |
     +----+----+----+----+----+----+----+----+

     |<------------------------------------->| rax
                         |<----------------->| eax
                                   |<------->|  ax
                                   |<-->|       ah
                                        |<-->|  al

这意味着当你写信给eax时,你也在写rax的下半部分(上半部分归零)。

所以,

                                         ; rax       eax          rdx       edx
; q = p++                                ; +----+----+----+----+  +----+----+----+----+
A1      mov     rax, QWORD PTR [rbp-8]   ; |                 p |  |               ??? |
A2      lea     rdx, [rax+4]             ; |                 p |  |               p+4 |
A3      mov     QWORD PTR [rbp-8], rdx   ; |                 p |  |               p+4 |
A4      mov     QWORD PTR [rbp-24], rax  ; |                 p |  |               p+4 |
; c = a++                                ; |                 p |  |               p+4 |
B1      mov     eax, DWORD PTR [rbp-40]  ; |       0 |       a |  |               p+4 |
B2      lea     edx, [rax+1]             ; |       0 |       a |  |       0 |     a+1 |
B3      mov     DWORD PTR [rbp-40], edx  ; |       0 |       a |  |       0 |     a+1 |
B4      mov     DWORD PTR [rbp-28], eax  ; |       0 |       a |  |       0 |     a+1 |
                                         ; +----+----+----+----+  +----+----+----+----+

【讨论】:

  • 你有点问题。在 64 位 x86 代码中,当指令的目标是 32 位寄存器时,处理器会自动将其零扩展至 64 位寄存器的高 32 位。所以表格下半部分的???? 实际上是0 的已知值。
  • @Michael Petch,谢谢,已修复。
  • @ikegami 感谢您提供良好的文字和图形,这真的很有帮助。但是由于我的英语表达不佳,我可能没有把我的问题说清楚,我在几分钟前改变了我的帖子,前往更精彩的答案。谢谢大家!
  • @ikegami 在您现在发表评论后,我得到了答复,非常感谢!这就是我真正想知道的。正如你所说,'rax'的值已经通过'eax'改变了。 :)
  • 如果这回答了您的问题,请检查答案旁边的标记。
【解决方案2】:

pq 是指向 int 的指针,int 的大小在您的平台上为 4。所以增加p 实际上会将其值增加4

int *q = p++;

   mov     rax, QWORD PTR [rbp-8]     ; rax = p
   lea     rdx, [rax+4]               ; same as rdx = rax + 4
   mov     QWORD PTR [rbp-8], rdx     ; p = rdx
   mov     QWORD PTR [rbp-24], rax    ; q = rax

c 是一个int。所以增加a 实际上只是将其值增加1

c = a++;

   mov     eax, DWORD PTR [rbp-40]    ; rax = a (yes modifying eax actually modifies rax)
   lea     edx, [rax+1]               ; same as edx = rax + 1
   mov     DWORD PTR [rbp-40], edx    ; a = edx
   mov     DWORD PTR [rbp-28], eax    ; c = eax (eax still contains the inital value of a)

More details about the LEA instructions here.

【讨论】:

  • Re "same as edx = rax + 1",或者更相关,same as edx = eax + 1。
  • OP 并不奇怪为什么有时会添加1,有时会添加4
【解决方案3】:

int *q = p++ 行中,地址指针正在递增。如您所知 int 的大小是 4 个字节,int 的大小是指针变量的大小,因此在汇编代码中您可以看到 lea rdx, [rax+4]
但是在int c = a++ 行中,变量a 的值正在增加。所以在汇编代码中你可以看到lea edx, [rax+1]

注意:int 的大小可能因编译器而异。但是根据基于GCC 的编译器,在你的情况下int 是4 字节长

【讨论】:

  • Re "size of int is size of pointer variable",不是OP的情况。
  • @ikegami x86,意思是 32 位不?哦,没关系,现在添加它。
  • @Stargateur, p 被读/写为QWORD,这意味着 64 位。 rax是用来存放的,是一个64位的寄存器。 /// 相比之下,a 被读/写为DWORD,这意味着 32 位。 eax是用来存放的,是一个32位的寄存器。
  • @cse, Re "你怎么能这么说?",我已经在后续评论中解释过了。
  • OP 并不奇怪为什么有时会添加1,有时会添加4
猜你喜欢
  • 1970-01-01
  • 2012-02-27
  • 1970-01-01
  • 2020-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-05
  • 1970-01-01
相关资源
最近更新 更多