gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 23:28:47 30 4
gpt4 key购买 nike

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

最佳答案

CDQE 指令将 EAX 寄存器中的 DWORD(32 位值)符号扩展为 RAX 寄存器中的 QWORD(64 位值)。
MOVZX 指令将源零扩展到目标。在这种情况下,它将在 [rbp-528+rax] 处从内存加载的 BYTE 符号扩展到 DWORD 目标寄存器 EAX
XOR eax, 1 指令只是翻转 EAX 的最低位。如果当前设置为 (1),则清除 (0)。如果当前清零 (0),则它变为设置 (1)。

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

但是,如果您愿意,我们无论如何都可以对其进行分析。这是您的 C 代码的整个汇编输出,由 GCC 8.2 在 -O0 生成,每条指令都带有注释:

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 指令外,其余代码应该很简单。

这是一 strip 进位的加法指令,用于在循环内无分支地编写条件 ifADC 执行以下操作:
destination = (destination + source + CF)

其中 CF 是进位标志。 CMP 指令在它设置进位标志之前,如果 visited[i] == 0 ,并且源是 0 ,所以它只是我在指令右侧评论的内容:如果 EAX ( counter ),它将 *RDX == 0 ( visited[i] == 0 ) 加 1 ;否则,它会添加 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

关于assembly - C 编译器输出的此代码中的 MOVZX、CDQE 指令的含义/用途是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54618685/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com