一开始先给出一段简单的C代码:
#include <stdio.h>
int main()
{
int i = 0;
for ( ;i < 3; i++)
{
int num = printf("hello\n");
if (num != 0)
{
break;
}
}
printf("aha,bye!\n");
return 0;
}
代码非常简单,for循环应该循环3次,但是这里为了更好的演示这个bug,在第一次执行循环内的printf()语句时,程序就会因满足if语句的条件而跳出循环,事实上for循环在这里只会执行一次,这显然不是什么bug。
那么bug在哪里呢?用VS2005或更新的版本编译这段C代码,我们希望在开始执行for循环时程序能断下来供我们分析,所以在for( ; i < 3; i++)这一句处按F9下个断点,按F5开始调试,程序居然直接执行完退出了,断点根本就没断下来,到底发生了什么?我们反汇编看一下就清楚了,先注释掉我们的if语句,现在for循环可以成功的执行三次,还是下同样的断点,开始调试,成功断下来后,切换到cmd窗口看一下,是不是已经输出了一次hello了?很奇怪吧,然后切换到反汇编界面,可以看到如下图所示:
从上图可以看到我们下的断点看起来确实是断在了for循环内的第一条指令0x004113B7地址上,但是再仔细看一下,当给变量i赋初值0后,在0x004113B5指令处直接jmp跳了下去,先去比较变量i是否小于3,如果大于等于3那么就通过jge跳出循环,如果小于3那么就执行printf()函数,输出完毕之后才会在0x004113E0处的这条jmp指令跳回到我们的断点处,也就是说第一次for循环的执行完全绕过了我们下的断点,如果第一次执行循环时通过break或者goto或者return之类的语句跳出循环了呢?或者第一次循环就不成立,0x004113C4处jge时就跳跑了呢?那么我们的断点就完全成了摆设,根本什么都断不下来!也就是我在一开始给出的那个小程序。
当然也不是所有的for循环都有这个小bug,只是当for循环条件的第一个语句为空时才会这样,如果你通常都是写for(i = 0; i < 3; i++)那么这个bug就不会影响到你了。
这个小bug很有意思,只有在VS2005以上的IDE环境下才会出现,而且应该都是Debug生成时才会这样,我试了一下VC6就没有这个问题,因为VC6认为0x004113B5处的jmp指令位于循环内,所以刚好断下来了,如下图:
两者的区别就是VC6如果在第一次断下后直接按F5继续运行,那么将一直运行到结束不会再次遇到这个断点,而VS2005虽然第一次循环断不下来,如果断下来之后按F5继续运行,那么每循环一次都会断一次。相比而言,我觉得还是VC6的行为更容易被接受,我调试程序时如果需要每次循环都中断,一般习惯将断点下在for循环的左大括号处,也就是上图的0x00401040这个地址处,如果希望第一次进入循环时中断,以后不再中断,则在for(...)这条语句处下断点。
我想这已经能算是VS2005下断点的一个bug了,因为用户完全有可能写出下面的代码:
int i = 0;
for ( ;i < 3; i++)
{
//do something
if (condition)
{
//do something
break;
}
}
如果第一次循环执行时就满足condition条件,那么断点将会什么都断不下来,一直运行到程序退出,就像最开始给出的那个代码一样。
即使程序不是刚好这么巧,第一次执行循环时就跳出了,那么VS2005命中我们的断点时,也已经是第二次了,就像上面的例子中已经输出了一次hello一样。