9.3 静态数据成员

9.1 节简单介绍了静态数据成员。当类中定义了静态数据成员时,由于静态数据成员和静态变量原理相同(是一个含有作用域的特殊全局变量),因此该静态数据成员的初值会被写入编译链接后的执行文件中。当程序被加载时,操作系统将执行文件中的数据读到对应的内存单元里,静态数据成员便已经存在,而这时类并没有实例对象。所以静态数据成员和对象之间的生命周期不同,并且静态数据成员也不属于某一对象,与对象之间是一对多的关系。静态数据成员仅仅和类相关,和对象无关,多个对象可以共同拥有同一个静态数据成员,如图9-4所示。

图 9-4 普通数据成员与静态数据成员区别

在图9-4中,定义了两个CStatic类对象staticOne和staticTwo。CStatic类的定义如下面的代码所示。根据图9-4中监视器窗口的显示,两个对象各自的数据成员在内存中的地址不同,而静态数据成员的地址却相同。可见,类中的普通数据成员对于同类对象而言是独立存在的,而静态数据成员则是所有同类对象的共用数据。静态数据成员和对象是一对多的关系。

因为静态数据成员有此特性,所以在计算类和对象的长度时,静态数据成员属于特殊的独立个体,不被计算在其中,如以下代码所示:


class CStatic{//类CStatic的定义

public:

static int m_snInt;//静态数据成员

int m_nInt;//普通数据成员

};

int CStatic:m_snInt=0;//静态数据成员初始化

void main(){

CStatic a;

int nSize=sizeof(a);//计算对象长度

//mov dword ptr[ebp-8],4;转换后的汇编代码,得到长度为4

printf("CStatic:%d\r\n",nSize);//显示对象长度

}


通过sizeof获得对象a所占用的内存长度为4。静态数据成员m_snInt没有参与对象a的长度计算。m_snInt为静态数据成员,m_nInt为普通数据成员,两者所属的内存地址空间不同,这也是静态数据成员不参与长度计算的原因之一,两者对比如下:


printf("0x%08x\r\n",&a.m_snInt);//使用对象直接调用静态数据成员

push offset CStatic:m_snInt(004237a4);静态成员所在地址为0x004237A4

;部分printf代码分析略

printf("0x%08x\r\n",&a.m_nInt);//获取普通数据成员地址

;获取对象的首地址并存入ecx中,得到数据成员m_nInt的地址

lea ecx,[ebp-4]

;部分printf代码分析略


在以上代码分析中,静态数据成员所在处的地址为0x004237A4,而普通数据成员的地址在ebp-4h中,是一个栈空间地址。在使用的过程中,静态数据成员是常量地址,可通过立即数间接寻址的方式访问。普通数据成员只有在类对象产生后才存在,地址值无法确定,只能以寄存器相对间接寻址的方式访问。所以,在成员函数中使用这两种数据成员时,由于静态数据成员属于全局变量,并且不属于任何对象,因此访问时无需this指针。而普通的数据成员属于对象所有,访问时需要使用this指针,如代码清单9-4所示。

代码清单9-4 在成员函数中使用静态数据成员与普通数据成员—Debug版


//C++源码说明:成员函数中使用静态数据成员与普通数据成员

class CStatic{//类CStatic的定义

public:

void ShowNumber();

static int m_snInt;//静态数据成员

int m_nInt;//普通数据成员

};

void CStatic:ShowNumber(){

printf("m_nInt=%d, m_snInt=%d",m_nInt, m_snInt);

}

void main(){

Static.m_nInt=1;

Static.m_snInt=2;

Static.ShowNumber();

}

//C++源码与对应汇编代码讲解

void main(){

CStatic Static;

;没有任何汇编代码

Static.m_nInt=1;

0040B788 mov dword ptr[ebp-4],1;普通数据成员赋值

Static.m_snInt=2;

0040B78F mov dword ptr[CStatic:m_snInt(004235bc)],2;静态数据成员赋值

Static.ShowNumber();

0040B799 lea ecx,[ebp-4];传递this指针

0040B79C call@ILT+5(CStatic:ShowNumber)(0040100a)

}

void CStatic:ShowNumber(){

0040B85A mov dword ptr[ebp-4],ecx;获取this指针

printf("m_nInt=%d, m_snInt=%d",m_nInt, m_snInt);

0040B85D mov eax,[CStatic:m_snInt(004235bc)];直接访问静态数据成员

0040B862 push eax

0040B863 mov ecx, dword ptr[ebp-4];获取this指针

0040B866 mov edx, dword ptr[ecx];通过this指针访问数据成员

0040B868 push edx

0040B869 push offset string"m_nInt=%d, m_snInt=%d"(00420fb0)

0040B86E call printf(00401080)

0040B873 add esp,0Ch

}


静态数据成员在反汇编代码中很难被识别,因为其展示形态与全局变量相同,很难被还原成对应的高级代码。可参考其代码的功能,酌情处理。