您的位置:首页 > 职场人生

关于指针与类的内存分布问题(问题思考来自《程序员面试宝典》)

2016-03-29 14:57 351 查看

引起思考的例子:

class A
{
public:
int a_1,a_2;
A() :a_1(1),a_2(2){}
void func()
{
cout << a_1 << endl;
cout << a_2 << endl;
}
};

class B
{
public:
int b;
B() :b(3){}
void func()
{
cout << b << endl;
}
};
int main()
{
A a;
B * pb = (B*)&a;
pb->func();
system("pause");
}

虽然是一个很有问题的代码,但是需要预测此时程序的行为。程序将输出 : 1

解释:

将 &a 强制类型转换为 B* ,因此在调用 pb->func() 的时候,编译器根据 pb 的类型将调用 B::func()。A中有两个成员 a_1,a_2。在A的内存布局中,其偏移量分别为0 与 1。B中有一个成员 b 。在 B 的内存布局中,其偏移量为 0。 在  B::func() 中,对 b 的访问在汇编语言层而言是对于相对 this 指针所指地址的偏移量为 0
的 int 成员的访问。此函数调用在编译时期不会出错。而在运行时期,通过对 this 指针所指对象的偏移量为 0 的地址访问。导致此时访问到了 A 中的 a_1 成员,该对象的此时的值为 1。因此输出为 1。

但是这种问题其实没什么好纠结的。是视编译器而定的内存布局。假设编译器对 A 对象的布局中,让 a_2 位于 a_1 的前方,则测试输出的值则为 2 了。测试一个真实的样例:让 A中有虚函数,则此时  A 的内存布局中有 vptr,会使得上述分析失效。如:

class A
{
public:
int a_1,a_2;
A() :a_1(1),a_2(2){}
void func()
{
cout << a_1 << endl;
cout << a_2 << endl;
}
virtual void f()     //与上述的代码完全相同,只是在A中增加了一个虚函数
{
}
};

此时,输出的结果为一个地址值、即为 A::Vptr 的值(在VS2013中)。说明在VS中,虚函数指针放在类对象内存布局的头部。因此对偏移量为 0 的访问则访问到了 A 的虚函数表的指针。若 B 中也有虚函数,则 A ,B 的布局相对来说是一样的。偏移量都包含了虚函数表的指针。则仍然符合上述分析。



一个思考:

类似上述问题,类中虚函数的调用也是与偏移量有关。因为虚函数在虚函数表中存储的就是偏移量。因此虚函数的调用可能会出现如下情况:

class A
{
public:
int a_1,a_2;
A() :a_1(1),a_2(2){}
void func()
{
cout << "afunc" << endl;
af();
}
virtual void af()
{
cout << "af" << endl;
}
};

class B
{
public:
int b;
B() :b(3){}
void func()
{
cout << "bfunc" << endl;
bf();          //对虚函数的调用转为对虚函数表中的偏移量对应的函数的调用
}
virtual void bf()
{
cout << "bf" << endl;
}
};
int main()
{
A a;
B * pb = (B*)&a;
pb->func();<span style="white-space:pre">			</span>//将会调用 B::func();
system("pause");
}


输出为:

bfunc

af

解释:
同理。pb->func() 调用的是 B::func(),因此输出了 bfunc 。然而由于 bf() 是B中的虚函数,而虚函数的调用会转为针对 vptr 的偏移量的调用,此时偏移量为 0 。但由于此时指针实际上指向的是 A 对象,因此 vptr 是A的 vptr 。同时 A 的 vptr 的偏移量为 0 的函数为 A::af(), 因此调用了 A::af()。并输出 af


对于继承的思考:

继承的多态体现在函数成员多态。而数据成员不具有多态性

class A
{
public:
int val;
A() :val(1){}

void f()
{
cout << "A" << endl;
cout << val << endl;
func();
}
virtual void func()
{
cout << "afunc" << endl;
}
};

class B : public A
{
public:
int val;
B() :val(2){}
void f()
{
cout << "B" << endl;
cout << val << endl;
}
virtual void func()
{
cout << "bfunc" << endl;
}
};

int main()
{
B b;
A * pa = &b;
pa->f();
system("pause");
}

输出为:
A

1

bfunc

解释:

非虚函数没有多态性,指针的静态类型决定了调用的函数,因此调用A::f()。而在 A::f() 中,数据成员的访问不具有多态性,因此 val 即为 A::val。因此输出 1。而虚函数 func 的调用则存在多态性,调用了 B::func();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息