下面是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
; ...
有谁能解释一下这段代码中CDQE
和MOVZX
指令的含义和用途吗?我也不明白XOR
指令有什么用。
1条答案
按热度按时间xienkqul1#
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
上生成,每个指令都有注解:汇编程序员和优化编译器都不会产生这种代码。它对寄存器的使用效率极低(更喜欢加载和存储到 memory,包括像
i
和counter
这样的值,这些值是存储在寄存器中的主要目标),并且它有很多无意义的指令。当然,优化编译器实际上会对这段代码做一些处理,完全省略它,因为它没有可观察到的副作用。输出将是:
这不是那么有趣的分析,但更有效。这就是为什么我们付给C编译器大笔巴克斯的原因。
C代码在以下行中也有未定义的行为:
您永远不会初始化
counter
,但之后您会尝试读取它。由于它是一个具有自动存储持续时间的变量,因此其内容不会自动初始化,并且从未初始化的变量中阅读是未定义的行为。这证明C编译器可以发出任何它想要的汇编代码。让我们假设
counter
初始化为0,我们手工编写汇编代码,忽略省略整个混乱的可能性。我们会得到这样的结果:发生了什么事?好吧,调用约定说
EAX
总是保存返回值,所以我把counter
放在EAX
中,并假设我们从函数返回counter
。RDX
是一个指针,用于跟踪visited
数组中的当前位置。它在整个MainLoop
中递增1(字节大小)。考虑到这一点,除了ADC
指令之外,其余代码应该很简单。这是一个带进位的加法指令,用于无分支地将条件
if
写入循环内部。ADC
执行以下操作:其中
CF
是进位标志。在它之前的CMP
指令设置进位标志ifvisited[i] == 0
,源代码是0
,所以它执行了我在指令右侧注解的操作:如果*RDX == 0
(visited[i] == 0
),则EAX
(counter
)加1;否则,它添加0(这是无操作)。如果你想写分支代码,你会这样做:
这同样有效,但取决于
visited
数组的值的可预测性,由于分支预测失败,可能会更慢。