test指令用于比较数值大小的可能性及推导

x86汇编语言中,cmp指令常用于两数值之间比较大小,test指令常用于位级判断。那么,test可不可以也用于比较数值大小呢?


提出问题

今天有朋友向我提出了这样的问题,p1
为什么test还有这种操作,set指令和test究竟是怎么凑到一起的。

查找信息

CMP指令与SUB指令的行为是一样的。在ATT格式中,列出操作数的顺序是相反的,这使代码有点难读。如果两个操作数相等,这些指令会将零标志位设置为1,而其他的标志可以用来确定两个操作数之间的大小关系。

TEST指令的行为和AND指令一样,除了它们只设置条件码而不改变目的寄存器的值。典型的用法是,两个操作数是一样的(例如,testq %rax, %rax用来检查%rax是负数,零,还是正数),或其中的一个操作数是一个掩码,用来指示哪些位应该被测试。

唔,看来课本只是重复了我们的问题,并没有给出解答。

典型的用法是,两个操作数是一样的(例如,testq %rax, %rax用来检查%rax是负数,零,还是正数)

但从这可以看出,test确实有这个用法。

p2
这里找到线索,set指令是和条件码直接关联的。那么就从test如何影响和改变条件码入手吧。

推导

机器是通过某些指令来改变条件码,然后通过判断条件码的组合来完成控制操作。个人理解是,这些控制指令是对条件码判断的一种封装。

回到我们纠结的这个问题上面:为什么test可以用于判断大小,以及 如何形成判断。

cmp的行为相对容易理解,右操作数减去左操作数,然后修改CF ZF SF OF的值。
test则是两个操作数进行,不会影响到OF CF,仅能改变SF ZF,确定真的可以用来比较大小吗?

不过,test用于比较大小的范围极窄,仅仅是用于判断符号,也就是和数值0作比较。只有这种特殊情况的话,进行推理则方便多了,大不了穷举嘛。

下面就来对比test S, Scmp $0,S

S看作有符号数,且大于0时:

  • test S, SSF置0,ZF置0,OF置0
  • cmp $0,SSF置0,ZF置0,OF置0

S看作有符号数,且等于0时:

  • test S, SSF置0,ZF置1,OF置0
  • cmp $0,SSF置0,ZF置1,OF置0

S看作有符号数,且小于0时:

  • test S, SSF置1,ZF置0,OF置0
  • cmp $0,SSF置1,ZF置0,OF置0

S看作无符号数,且等于0时:

  • test S, SZF置1,CF置0
  • cmp $0,SZF置1,CF置0

S看作无符号数,且不等于0时:

  • test S, SZF置0,CF置0
  • cmp $0,SZF置0,CF置0

惊奇地发现这两句指令的行为是一模一样的。原因在于cmp在与0作比较时,是不可能发生溢出进(借)位的,因而不可能影响OF CF位,这和test指令是一样的。剩下的SFZF很容易推理,这里不赘述。

实验

对这样一段C代码进行汇编处理。

1
2
3
4
int test(int a)
{
return a > 0;
}

不进行优化

1
gcc -S test.c

得到汇编代码(节选):

1
2
3
movl %edi, -4(%rbp)
cmpl $0, -4(%rbp)
setg %al

进行 O1 优化

1
gcc -S -O1 test.c

得到汇编代码(节选):

1
2
testl %edi, %edi
setg %al

事实证明,test确实可以用于与0进行大小比较,且编译器更愿意优化成这种形式。

结论与反思

当我们难以理解指令的逻辑时,则需要去探究它背后具体的操作过程,才能真正地理解操作的实际效果。