调试环境:xpsp3 vs2005
最近在写一个小工具,中途遇到一个小bug,调试了我个把小时才完全搞清楚是怎么回事……下面是一段经过高度简化的代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
void staticFunction(int i)
{
static int nflag = i;
cout << nflag << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
for (int i = 0; i < 5; ++i)
{
staticFunction(i);
}
return 0;
}
可以先猜测一下,这段代码的输出情况,是不是有一点点出乎意料。我机器上面的输出如下:
0
0
0
0
0
为什么不是0 1 2 3 4呢?调试的时候发现,每次跟nflag赋值的时候,nflag的值没有任何变化,开始以为赋值前后有代码操作数据的时候越界了(原始代码是比较长的),检查了半天,把整个函数的代码几乎都删空了发现还是这样,后来发现nfag变量被static修饰,把static删除之后,逻辑正常了!
这……这个static在这里,是因为之前nflag变量是个标志位变量,用来标注函数的状态的,后来因为修改之后通过传参数来设置状态,static忘记删除了。问题是为什么有static修饰时,赋值会失败呢?看来只有从汇编代码中找答案了……
static int nflag = i;
004113CE mov eax,dword ptr [$S1 (4171A0h)]
004113D3 and eax,1
004113D6 jne staticFunction+3Dh (4113EDh)
004113D8 mov eax,dword ptr [$S1 (4171A0h)]
004113DD or eax,1
004113E0 mov dword ptr [$S1 (4171A0h)],eax
004113E5 mov eax,dword ptr [i]
004113E8 mov dword ptr [nflag (41719Ch)],eax // 赋值
cout << nflag << endl;
004113ED mov esi,esp
004113EF mov eax,dword ptr [__imp_std::endl (4182A0h)]
004113F4 push eax
004113F5 mov edi,esp
004113F7 mov ecx,dword ptr [nflag (41719Ch)]
004113FD push ecx
004113FE mov ecx,dword ptr [__imp_std::cout (41829Ch)]
00411404 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (418298h)]
很明显,变量地址是0x41719C,但是给他赋值之前,先进行了一个判断,如果是已经赋值过了(根据存在0x4171A0下面的一个标志位),则再次进函数的时候就不再赋值了,直接使用(参考:004113D6 jne staticFunction+3Dh (4113EDh))。
后来查了下资料才知道这是C++的语法所规定的,函数里面的静态变量只能被初始化一次。去掉static修饰之后,代码就简单了:
/*static*/ int nflag = i;
004113CE mov eax,dword ptr [i]
004113D1 mov dword ptr [nflag],eax
cout << nflag << endl;
004113D4 mov esi,esp
004113D6 mov eax,dword ptr [__imp_std::endl (4182A0h)]
如果用常量初始化静态变量呢?如下:
004113CC rep stos dword ptr es:[edi]
static int nflag = 100;
cout << nflag << endl;
004113CE mov esi,esp
004113D0 mov eax,dword ptr [__imp_std::endl (4182A0h)]
004113D5 push eax
004113D6 mov edi,esp
004113D8 mov ecx,dword ptr [nflag (417034h)]
004113DE push ecx
004113DF mov ecx,dword ptr [__imp_std::cout (41829Ch)]
004113E5 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (418298h)]
可以发现,直接就用了,看一下地址0x417034h的值,已经是100了,因此用常量赋值的情况,在编译时期,值就已经写到数据区了。
【END】