在实际编程过程中我们经常使用到静态数据成员,可是静态数据成员究竟和普通数据成员有什么区别呢,为什么会是所有对象共享静态成员,是对象中有指针指向静态数据的位置吗?让我们走进C++背后的故事
当在类中定义静态数据成员时,由于静态数据成员和静态变量(一个含有作用域的特殊全局变量),因此静态数据成员的初值会被写入到编译链接后的执行文件中的。
当程序被加载时,操作系统将文件中的数据读到对应的内存单元里,这时类还没有实例对象,但此时静态数据成员已存在了。所以静态数据成员的生命周期与该类的对象不同,且静态数据成员不属于任何某一对象。
测试代码:
class CStatic
{
public:
static int m_snInt;
int m_nInt;
};
int CStatic::m_snInt = 1;
int main(int argc, char *argv[])
{
CStatic a;
a.m_nInt = 1;
int SizeA = sizeof(a);
printf("0x%08x\n", &a.m_nInt);
printf("0x%08x\n", &a.m_snInt);
CStatic b;
b.m_nInt = 2;
int SizeB = sizeof(b);
printf("0x%08x\n", &b.m_nInt);
printf("0x%08x\n", &b.m_snInt);
system("pause");
return 0;
}
查看其内存分布情况:
从这里可以看到 对象的大小是4个字节,并没有包含静态成员的大小,也进一步证明了为静态成员分配的内存并不在对象中。从m_nInt 和m_snInt 的地址可以看出他们的地址相距较远,因为一个分配在栈上,一个在程序加载时就已分配在了数据区。
我们再从反汇编的角度看一看访问静态数据成员和普通数据成员的区别:
测试代码:
class 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);
}
int CStatic::m_snInt = 1;
int main(int argc, char *argv[])
{
CStatic a;
a.m_nInt = 1;
a.m_snInt = 2;
a.ShowNumber();
system("pause");
return 0;
}
反汇编代码:
int main(int argc, char *argv[])
{
01371420 push ebp
01371421 mov ebp,esp
01371423 sub esp,0D0h
01371429 push ebx
0137142A push esi
0137142B push edi
0137142C lea edi,[ebp-0D0h]
01371432 mov ecx,34h
01371437 mov eax,0CCCCCCCCh
0137143C rep stos dword ptr es:[edi]
0137143E mov eax,dword ptr ds:[01378004h]
01371443 xor eax,ebp
01371445 mov dword ptr [ebp-4],eax
CStatic a;
a.m_nInt = 1;
01371448 mov dword ptr [a],1
a.m_snInt = 2;
0137144F mov dword ptr ds:[1378000h],2
a.ShowNumber();
01371459 lea ecx,[a]
0137145C call CStatic::ShowNumber (013711E0h)
从地址0137144F 来看其赋值过程,可以看出静态变量的地址为常数值(在数据区),我们也可以从内存中来印证这一点:
再进入ShowNumber函数体看看:
void CStatic::ShowNumber()
{
013713C0 push ebp
013713C1 mov ebp,esp
013713C3 sub esp,0CCh
013713C9 push ebx
013713CA push esi
013713CB push edi
013713CC push ecx
013713CD lea edi,[ebp-0CCh]
013713D3 mov ecx,33h
013713D8 mov eax,0CCCCCCCCh
013713DD rep stos dword ptr es:[edi]
013713DF pop ecx
013713E0 mov dword ptr [this],ecx
//从普通成员函数中观察访问两种数据成员之中的差别
printf("m_nInt = %d, m_snInt = %d", m_nInt, m_snInt);
013713E3 mov esi,esp
013713E5 mov eax,dword ptr ds:[01378000h]
013713EA push eax
013713EB mov ecx,dword ptr [this]
013713EE mov edx,dword ptr [ecx]
013713F0 push edx
013713F1 push 13759DCh
013713F6 call dword ptr ds:[1379118h]
从这段汇编代码可以看出,一个是通过this指针来寻找普通成员的位置,一个是直接通过常亮地址直接找到静态成员的位置。
这也是为什么普通数据成员一定需要对象的首地址才能访问,而静态数据成员既可以通过类名访问,也可以通过对象名进行访问(因为编译器在编译期会将其直接替换为静态数据成员的常量地址)