C编译器输出的此代码中MOVZX,CDQE指令的含义/用途是什么?

时间:2019-02-10 16:48:36

标签: assembly x86 64-bit x86-64

我有以下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指令的用途。

1 个答案:

答案 0 :(得分:2)

CDQE指令将EAX寄存器中的DWORD(32位值)符号扩展为RAX寄存器中的QWORD(64位值)。

MOVZX指令将源零扩展到目标。在这种情况下,它会将在[rbp-528+rax]处从内存加载的BYTE符号扩展到DWORD目标寄存器EAX

XOR eax, 1指令仅翻转EAX的最低位。如果当前设置为(1),则将其清除(0)。如果当前清除状态为(0),则将其设置为(1)。

总体情况如何?好吧,事实证明,这几乎是毫无意义的代码,是您在未启用优化的情况下从编译器获得的输出。尝试分析它的目的很少。

但是,如果您愿意,我们仍然可以对其进行分析。这是GCC 8.2在-O0处生成的C代码的整个程序集输出,其中每条指令都带有注释:

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之类的值,它们是存储在寄存器中的主要目标),并且有很多毫无意义的说明。

当然,一个优化的编译器确实会对这个代码进行大量处理,因为它没有明显的副作用,因此完全将其消除了。输出将是:

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

分析起来没那么有趣,但是效率更高。这就是为什么我们向C编译器支付巨额费用的原因。

C代码在以下几行中也具有未定义的行为:

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

您永远不会初始化counter,但是您尝试从中读取。由于它是具有自动存储持续时间的变量,因此不会自动初始化其内容,并且从未初始化的变量读取是未定义的行为。这证明C编译器可以发出所需的任何汇编代码。

让我们假设counter初始化为0,我们将手工编写此汇编代码,而忽略了消除整个混乱的可能性。我们会得到类似的东西:

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始终保留返回值,因此我将counter放在EAX中,并假定我们从函数中返回counterRDX是一个指针,用于跟踪visited数组中的当前位置。在整个MainLoop中,它递增1(BYTE的大小)。考虑到这一点,除ADC指令外,其余代码应简单明了。

这是一个带进位加法指令,用于将条件if无条件地写入循环内部。 ADC执行以下操作:

destination = (destination + source + CF)

其中CF是进位标志。 CMP指令位于visited[i] == 0之前,它设置进位标志,而源代码是0,因此它的作用与我在指令右边的注释相同:它将1加到{如果EAXcounter)为{1}}(*RDX == 0);否则,它将加0(这是一个空操作)。

如果要编写分支代码,请执行以下操作:

visited[i] == 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 数组的值的可预测性,可能是slower due to branch prediction failure