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

引起思考的例子:

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();


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值