什么是这个代码输出的意义/使用的MOVZX,CDQE指令可由C编译器?

问题描述 投票:0回答:1

我有下面的C代码片段:

int main() {

    int tablica [100];
    bool visited [100];
    int counter;
    int i;

    for(i=0;i<=99;i++) {
        if (visited[i]==0) {
            counter=counter+1;
        }
    }

}

我转换成汇编程序。我收到了以下的输出:

   ; ...

    mov     eax, DWORD PTR [rbp-8]
    cdqe
    movzx   eax, BYTE PTR [rbp-528+rax]
    xor     eax, 1
    test    al, al
    je      .L3

    ; ...

可能有人给我解释一下什么是这个代码CDQEMOVZX指令的意义和目的是什么?我也不懂使用XOR的指令是什么。

assembly x86 64bit x86-64
1个回答
3
投票

所述CDQE指令在签到延伸的DWORD(32位值)EAXRAX寄存器注册到QWORD(64位值)。

所述MOVZX指令零扩展的源到目的地。在这种情况下,通过符号-延伸[rbp-528+rax]从存储器加载到所述DWORD目的地寄存器,EAX的字节。

XOR eax, 1指令只是翻转EAX的最低位。如果当前设置的(1),则它变得清晰(0)。如果当前(0)清除,则它被置(1)。

什么是大局?那么,事实证明,这几乎是完全没有意义的代码,输出您从编译器得到未启用优化的那种。它提供很少的目的是尝试和分析。

但是,如果你想要,我们可以反正分析它。这里是整个组件输出C代码,如在-O0由GCC 8.2产生,带有加注解的每个指令:

main():
        push    rbp                         ; \ standard function
        mov     rbp, rsp                    ; /  prologue code
        sub     rsp, 408                    ; allocate space for stack array
        mov     DWORD PTR [rbp-8], 0        ; i = 0
.L4:
        cmp     DWORD PTR [rbp-8], 99       ; is i <= 99?
        jg      .L2                         ; jump to L2 if i > 99; otherwise fall through
        mov     eax, DWORD PTR [rbp-8]      ; EAX = i
        cdqe                                ; RAX = i
        movzx   eax, BYTE PTR [rbp-528+rax] ; EAX = visited[i]
        xor     eax, 1                      ; flip low-order bit of EAX (EAX ^= 1)
        test    al, al                      ; test if low-order bit is set?
        je      .L3                         ; jump to L3 if low-order bit is clear (== 0)
                                            ;  (which means it was originally set (== 1),
                                            ;   which means visited[i] != 0)
                                            ; otherwise (visited[i] == 0), fall through
        add     DWORD PTR [rbp-4], 1        ; counter += 1
.L3:
        add     DWORD PTR [rbp-8], 1        ; i += 1
        jmp     .L4                         ; unconditionally jump to top of loop (L4)
.L2:
        mov     eax, 0                      ; EAX = 0 (EAX is result of main function)
        leave                               ; function epilogue
        ret                                 ; return

既不是组件程序员也不是优化编译器将产生该代码。它使极其低效利用寄存器(宁愿加载和存储到内存中,包括像icounter值,这是在寄存器中存储的首要目标),并且它有很多无谓的指令。

当然,一个优化的编译器会真的对这个代码的数字,完全eliding它,因为它没有可观察到的副作用。输出也只是:

main():
        xor     eax, eax    ; main will return 0
        ret

这不是那么有趣的分析,但更高效。这就是为什么我们付出我们的C编译器一掷千金。

C代码也有这些行未定义的行为:

int counter;
/* ... */
counter=counter+1;

你永远不会初始化counter,但你尝试从中读取数据。因为它是具有自动存储持续时间的变量,它的内容不会被自动初始化,并从一个未初始化的变量读取是未定义的行为。这证明C编译器发射其希望的任何汇编代码。

让我们假设counter被初始化为0,而我们手工编写本汇编代码,忽略eliding全乱的可能性。我们希望得到的东西,如:

main():
        mov     edx, OFFSET visited             ; EDX = &visited[0]
        xor     eax, eax                        ; EAX = 0
MainLoop:
        cmp     BYTE PTR [rdx], 1               ; \ EAX += (*RDX == 0) ? 1
        adc     eax, 0                          ; /                    : 0
        inc     rdx                             ; RDX += 1
        cmp     rdx, OFFSET visited + 100       ; is *RDX == &visited[100]?
        jne     MainLoop                        ; if not, keep looping; otherwise, done
        ret                                     ; return, with result in EAX

发生了什么?那么,调用约定说,EAX始终保存了返回值,所以我在counterEAX和假定我们是从函数返回counterRDX是一个指针跟踪visited阵列中的当前位置。它被由1整个MainLoop(一个字节的大小)递增。考虑到这一点,在代码的其余部分应简洁明了,除了ADC指令。

这是一个附加与进位指令,用于写循环branchlessly的内部条件if。一个ADC执行以下操作:

destination = (destination + source + CF)

其中CF是进位标志。该CMP指令它设置权前的进位标志,如果visited[i] == 0,源是0,所以它正是我评论的指令的权利:它增加了1至EAXcounter)如果*RDX == 0visited[i] == 0);否则,它增加了0(这是一个无操作)。

如果你想写枝代码,你会怎么做:

main():
        mov     edx, OFFSET visited             ; EDX = &visited[0]
        xor     eax, eax                        ; EAX = 0
MainLoop:
        cmp     BYTE PTR [rdx], 0               ; (*RDX == 0)?
        jne     Skip                            ; if not, branch to Skip; if so, fall through
        inc     eax                             ; EAX += 1
Skip:
        inc     rdx                             ; RDX += 1
        cmp     rdx, OFFSET visited + 100       ; is *RDX == &visited[100]?
        jne     MainLoop                        ; if not, keep looping; otherwise, done
        ret                                     ; return, with result in EAX

该作品一样好,但根据visited阵列的值是如何预见的是,可能是slower due to branch prediction failure

© www.soinside.com 2019 - 2024. All rights reserved.