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