关系与逻辑运算的反汇编与优化

写在前面

本文讨论了c/c++中关系和逻辑运算的反汇编变现形式以及优化方案,旨在记录笔者的学习过程,也希望对各位读者有所帮助。

关系运算

首先我们来说说关系运算,关系运算有大于、等于、小于、大于等于、小于等于等等。不同的关系运算对应的汇编跳转指令是不一样的。接下来给出一张表,表中列出了汇编跳转指令的含义以及检查的标志位。

表达式短路

现在我们来看看短路与和短路或在汇编层面的表示形式。

先看看短路与,短路与的真值条件为两个表达式为真才为真,下面给出我们的示例代码:

#include <stdio.h>

int accumulation(int n)
{
	n && (n += accumulation(n - 1));

	return n;
}

int main(int argc, char* argv[])
{
	accumulation(10);

	return 0;
}

代码十分简单,使用了递归来求累加和。在debug编译选项下生成程序,我们来看看程序的汇编形式。

我们可以看到,程序先用0和n相比较,使je指令进行跳转,跳转的地址是函数的返回语句处。如果跳转失败,则递减n的值,继续调用递归函数。将汇编代码和源代码对比一下,很容易想通其中的关系。

接下来我们使用短路或实现上述程序的功能。源代码如下:

#include <stdio.h>

int accumulation(int n)
{
	(n == 0) || (n += accumulation(n - 1));

	return n;
}

int main(int argc, char* argv[])
{
	accumulation(10);

	return 0;
}

对应的汇编代码如下:

对比短路与的汇编代码我们可以发现,两种短路虽然逻辑不同,但对应的汇编代码却是相同的。

三目运算

许多人想当然地认为三目运算就是按照普通的分支跳转来编译,其实不然,编译器会根据三目运算表达式的不同来进行优化。由于现代处理器的特性,编译器会尽可能地避免生成包含条件分支跳转的代码(以后会专门出一篇短文章介绍现代处理器的分支预测功能)。

三目运算的基本表现形式为:A ? B : C。对A、B、C三个表达式的以下四种情况,编译器会做出无分支优化:

  1. 表达式A为简单比较,而表达式B和表达式C两者为常量且差值等于1。
  2. 表达式B和表达式C两者为常量且差值大于1。
  3. 表达式B或表达式C有变量。
  4. 表达式或B表达式C有变量表达式。

此外,当表达式A为常量表达式时,编译器会在编译期得出结果,不会生成汇编代码。接下来我们来分情况讨论。

第一情况的优化

第一种情况是表达式A为简单表达式,B、C为常量且差值为1。我们先给出源代码:

#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("n = %d\n", argc == 5 ? 5 : 6);

	return 0;
}

我们在Release编译选项下生成并调试代码,反汇编窗口如下图:

可以看到,在第三行使用了cmp指令对5和argc进行比较,在加载了printf函数的第一个参数的地址后,执行了setne指令,这条指令的意思是:将edx寄存器的值设置为对ZF标志的值取反。我们知道,cmp指令影响的标志位之一就是ZF位,ZF标志位是0标志位,相关指令执行后结果为0那么ZF=1,结果不为0则ZF=0。在这里,cmp指令的两个擦作数如果相等,则比较结果为0,此时ZF为1,反之ZF则为0。由此不难理解,编译器把分支跳转指令优化为了串行指令,避免了分支预测错误带来对性能的影响。

第二种情况的优化

第二种情况是表达式B、C为常量且差值大于1

#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("%d\n", argc > 5 ? 4 : 10);

	return 0;
}

同样在Release模式下编译并调试代码:

在调用printf函数之前,调用了cmovg条件传输指令,意思是比较结果大于则执行传输。结合前面的cmp指令和mov指令,不难看出编译器的优化之处。

第三种情况的优化

第三种情况是表达式B、C其中一个为变量表达式。

#include <stdio.h>

int main(int argc, char* argv[])
{
	int n1, n2;
	scanf_s("%d %d", &n1, &n2);
	printf("%d \n", argc ? n1 : n2);

	return 0;
}

我们可以看到,这里同样使用了cmovne指令进行优化,把分支语句变为了无分支语句。

第四种优化情况

最后一种情况是表达式B、C均为变量表达式。

#include <stdio.h>

int main(int argc, char* argv[])
{
	int n1, n2;
	scanf_s("%d %d", &n1, &n2);
	printf("%d \n", argc ? n1 : n2 + 3);

	return 0;
}

可以看到,对于第四种情况,编译器没有做出什么优化,只是使用简单的分支跳转语句。这会触发处理器的分支预测机制,因此在复杂的条件表达式中,使用分支通常比使用条件传送的性能更高。

posted @ 2025-05-30 17:06  XueZhou  阅读(10)  评论(0)    收藏  举报