C++类内部存储分析(含源码)(原创+转载)

转载自:http://blog.csdn.net/cxsjabcabc/article/details/7623630

 

源代码下载地址:

http://www.rayfile.com/zh-cn/files/c5d15e54-c18e-11e1-a565-0015c55db73d/ 

1、 为什么一个不包含任何成员变量的类的大小不是0?

如下代码:

#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;


class Test
{
public:
	Test()							// 构造函数不占类大小
	{
		std::cout << "Test is called..." << std::endl;
	}

	~Test()							// 析构函数不占类大小
	{
		std::cout << "~Test is called..." << std::endl;
	}
	void show() const { }			// 非虚函数不管有几个,都不占用类大小
	void show1(){}
	void show2(){}
	void show3(){}

	//virtual void virtual_show()  { }// 只要出现一个虚函数,不管有几个,开始占用4个字节
	//virtual void virtual_show1() { }
	//virtual void virtual_show2() { }

};

int main (int argc, const char * argv[])
{
	COUT_ENDL(sizeof(Test))
	system("pause");
	return 0;
}

 

运行结果:

sizeof(Test) is 1  


A: 
(1)这在于,如果使用Test创建一个对象,那么这个对象的大小如果是0的话, 可能造成不同对象具有相同的地址;这可能导致之后的访问出现不明确的状态。所以,一般,编译器会给大小为0的类中自动插入一个字节的大小,具体多大可能取决于编译器。我们能够肯定的是,它一定不是0.
(2)类里的构造函数与析构函数、非虚函数,不占类大小.
(3)如果出现了虚函数,只要出现一个虚函数,不管有几个,开始占用4个字节。
为了证明这一点,我们把上面的代码改为:
 
#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;


class Test
{
public:
	Test()							// 构造函数不占类大小
	{
		std::cout << "Test is called..." << std::endl;
	}

	~Test()							// 析构函数不占类大小
	{
		std::cout << "~Test is called..." << std::endl;
	}
	void show() const { }			// 非虚函数不管有几个,都不占用类大小
	void show1(){}
	void show2(){}
	void show3(){}

	virtual void virtual_show()  { }// 只要出现一个虚函数,不管有几个,开始占用4个字节
	virtual void virtual_show1() { }
	virtual void virtual_show2() { }

};

int main (int argc, const char * argv[])
{
	COUT_ENDL(sizeof(Test))
	system("pause");
	return 0;
}

运行结果:

sizeof(Test) is 4
2、  Test类的构造函数占用类对象的空间吗?

A: 根据上面sizeof得到的值,它应该是不占用的。从实际上说,从代码段的角度,它们和c函数中的全局函数没什么不同;只不过如何访问它们可能有限制而已。经过测试,发现如果想直接获取Test类构造函数的地址,赋值给c风格函数指针,编译器总是报错,必须采用指向类成员函数指针的方式才能使用,所以这里无法给出一个很好证明它类似全局函数的例子。


3、如果类中含成员变量,可以通过获取此类对象的地址绕开访问权限函数来修改对象内部数据吗?

A: 通过地址来修改数据,c++无法阻止这样的操作,只要它不越界访问。如下代码:

#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

private:
	int _value_one;
	int _value_two;
};

int main (int argc, const char * argv[])
{
	A a(10, 100);
	COUT_ENDL(a.value_one())
	COUT_ENDL(a.value_two())

	A *pa = &a;
	*(int *)pa = 11;            // modify a's _value_one
	*((int *)pa + 1) = 111;     // modify a's _value_two

	COUT_ENDL(a.value_one())
	COUT_ENDL(a.value_two())
	system("pause");
	return 0;
}


 

运行结果:

a.value_one() is 10  
a.value_two() is 100  
a.value_one() is 11  
a.value_two() is 111  


 

显然,此方法有效,而且根本不用担心所操作对应的成员是否是protected或者private的

而且可以确定的是,对象a中成员_value_one地址为a首地址;_value_two的地址紧挨着成员_value_one.


4、 如果具备继承体系的类对象,成员如何摆放?

A: 如下示例:

#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

private:
	int _value_one;
	int _value_two;
};

class A_Ex : public A
{
public:
	A_Ex(int one, int two, int three):A(one, two), _value_three(three) { }

	int value_three() const { return _value_three; }

private:
	int _value_three;
};

int main (int argc, const char * argv[])
{
	A_Ex a(10, 100, 1000);

	A *pa = &a;
	COUT_ENDL(*(int *)pa)            
	COUT_ENDL(*((int *)pa + 1))    
	COUT_ENDL(*((int *)pa + 2))  
	system("pause");
	return 0;
}


 

运行结果:
 
*(int *)pa is 10  
*((int *)pa + 1) is 100  
*((int *)pa + 2) is 1000 



可以看出,A_Ex类继承A类,它的对象首先存放基类成员信息,接着存放本类成员信息,很简单,很直接。


5、  如果类中含有虚函数,类大小会发生怎样的变化?

A: 如下代码:

#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

	virtual void show() const { }

private:
	int _value_one;
	int _value_two;
};

class A_Ex : public A
{
public:
	A_Ex(int one, int two, int three):A(one, two), _value_three(three) { }

	int value_three() const { return _value_three; }

private:
	int _value_three;
};

int main (int argc, const char * argv[])
{
	A a(10, 100);
	A_Ex a_ex(97, 98, 99);
	COUT_ENDL(sizeof(A))
	COUT_ENDL(sizeof(A_Ex))
	system("pause");	
	return 0;
}


 

运行结果(生成32位应用程序):

C++类内部存储分析(含源码)(原创+转载)_第1张图片

如果在函数结束前加上断点,并打印变量a和a_ex内部数据,得到如下:

很明显,可以看到,虚表指针占用了对象最初的部分,后面接着是成员变量。


6、  虚表指针到底在哪里?

A: 根据上面的输出数据,虚表指针保存在对象首地址处。下面将来证明它的存在:

#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

	virtual void show() const {  std::cout << "show A..." << std::endl; }

private:
	int _value_one;
	int _value_two;
};

class A_Ex : public A
{
public:
	A_Ex(int one, int two, int three):A(one, two), _value_three(three) { }

	int value_three() const { return _value_three; }
	virtual void show() const { std::cout << "show A_Ex..." << std::endl; }

private:
	int _value_three;
};

typedef void (*c_style_show)();

int main (int argc, const char * argv[])
{
	A a(10, 100);
	A_Ex a_ex(97, 98, 99);

	int *p_vtable = (int *)&a;  // 获得a的地址,也是指向虚表的地址
	COUT_ENDL(p_vtable)
	int *vtable = (int *)*p_vtable; // 获得虚表的指针 
	COUT_ENDL(vtable)

	// 获得每一个虚函数的地址
	int *firstVirtualFunc = (int *)vtable[0];
	COUT_ENDL(firstVirtualFunc)

	// 指导函数的地址强转为c_style函数,并调用它
	c_style_show show_func = (c_style_show)firstVirtualFunc;
	show_func();

	system("pause");
	return 0;
}

 
 
 
上面的代码,是要刻意取出虚函数表中第一个函数指针( 理论上就会是show函数).

对象a虚表指针以及虚表内部函数指针的关系简图如下:

C++类内部存储分析(含源码)(原创+转载)_第2张图片


由上图,可知,show_func将被赋值为A类中的show函数。执行结果如下:


同理,可以对a_ex对象进行剖析,找出它的虚表和虚表函数。

其实,从上面的分析,也能间接证明类中的成员函数也可以看成全局函数的特殊形式,只是访问方式有所限制。


7、 当子类和父类的大小不一致的时候,出现的对象切割,该如何理解?

A: 如果非要将一个较大的对象赋值给较小的对象,而且没有按照特定的转换方式,终究可能会发生数据丢失的问题。如下例子:

 
//=============== 7、 当子类和父类的大小不一致的时候,出现的对象切割,该如何理解?==========================================
#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

	virtual void show() const {  std::cout << "show A..." << std::endl; }


private:
	int _value_one;
	int _value_two;
};

class A_Ex : public A
{
public:
	A_Ex(int one, int two, int three):A(one, two), _value_three(three) { }

	int value_three() const { return _value_three; }
	virtual void show() const { std::cout << "show A_Ex..." << std::endl; }

private:
	int _value_three;
};


int main (int argc, const char * argv[])
{
	A *pa = new A(10, 100);
	A_Ex *pa_ex = new A_Ex(97, 98, 99);

	pa->show();		// show A...
	pa_ex->show();	// show A_Ex...

	*pa = *pa_ex;	// 引起分割;但是并没有影响pa对象调用show的结果
	pa->show();     // show A...
	std::cout << pa->value_one() << endl;		// 97   数据被复制过去了
	std::cout << pa->value_two() << endl;		// 98
	pa_ex->show();  // show A_Ex...

	delete pa_ex;
	delete pa;

	/*
	// 每二种测试方法:
	A a(10, 100);
	A_Ex a_Ex(97, 98, 99);

	a = a_Ex;
	a.show();      // show A...
	std::cout << a.value_one() << endl;		// 97   数据被复制过去了
	std::cout << a.value_two() << endl;		// 98
	*/



	system("pause");
	return 0;
}

输出结果:
  C++类内部存储分析(含源码)(原创+转载)_第3张图片
 
可以看出,*pa = *pa_ex; 虽然造成了对象切割,但是并没有影响pa对象调用show的结果。调试,具体查看下被切割后的结果:

C++类内部存储分析(含源码)(原创+转载)_第4张图片


在两个断点处打印*pa的数据:

C++类内部存储分析(含源码)(原创+转载)_第5张图片


可以看出

(1)*pa数据确实被对象a_ex的数据替换了

(2)不过也可以看出对象a的虚表指针依然没变,导致了调用show函数没有改变


8、 可以改变对象a的虚表指针的值吗?

A: 当然可以。如下代码:

#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

	virtual void show() const {  std::cout << "show A..." << std::endl; }
	// 类A中增加了重载赋值运算符的函数,且内部将对象首地址的虚表指针更改
	A & operator=(const A& a)
	{
		if(this != &a)
		{
			*(int *)this = *(int *)&a;
			_value_one = a._value_one;
			_value_two = a._value_two;
		}
		return *this;
	}

private:
	int _value_one;
	int _value_two;
};

class A_Ex : public A
{
public:
	A_Ex(int one, int two, int three):A(one, two), _value_three(three) { }

	int value_three() const { return _value_three; }
	virtual void show() const { std::cout << "show A_Ex..." << std::endl; }

private:
	int _value_three;
};


int main (int argc, const char * argv[])
{
	A *pa = new A(10, 100);
	A_Ex *pa_ex = new A_Ex(97, 98, 99);

	pa->show();
	pa_ex->show();

	*pa = *pa_ex;   // cause slice
	pa->show();
	pa_ex->show();

	delete pa_ex;
	delete pa;

	system("pause");
	return 0;
}


 可以看出把派生类的空间内容拷到基类空间中(*pa = *pa_ex; // cause slice),即使相应的成员是虚函数,但还是无法把派生类的此成员函数拷贝过去。
请注意,以上  把派生类的空间内容拷到基类空间
与  本文中第9点例子中讲到的基类指针指向派生类指针  是两种不同的操作,如果是进行基类指针指向派生类指针操作,

且是非虚函数,那么即使重载了赋值运行符也是不能把内部圣像首地址虚表指针更改的。(请参照第9点)

 

 

类A中增加了重载赋值运算符的函数,且内部将对象首地址的虚表指针更改

运行结果:

show A...  
show A_Ex...  
show A_Ex...  
show A_Ex...  
可以看出,对象a的虚表指针已经被更改。


 

9、 当基类指针指向继承类引起的非virtual地址替换? 
//=============== 9、 当基类指针指向继承类引起的virtual地址替换?==========================================
#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
	int value_one() const { return _value_one; }
	int value_two() const { return _value_two; }

	void show() const {  std::cout << "show A..." << std::endl; }



private:
	int _value_one;
	int _value_two;
};

class A_Ex : public A
{
public:
	A_Ex(int one, int two, int three):A(one, two), _value_three(three) { }

	int value_three() const { return _value_three; }
	void show() const { std::cout << "show A_Ex..." << std::endl; }


private:
	int _value_three;
};


int main (int argc, const char * argv[])
{
	A *pa_Degret = NULL;
	A_Ex a_Ex(97, 98, 99);


	a_Ex.show();				// show A_Ex...
	// !!!!!!!!!!特别注意!!!!!!!!!!!!!!!!!!!
	// 当是同名函数, 但此函数不是虚的,继承类中的此函数也能覆盖基类函数

	pa_Degret = &a_Ex;			// 它不调用赋值函数,只是指针的变化
	pa_Degret->show();			// show A...
	// !!!!!!!!!!特别注意!!!!!!!!!!!!!!!!!!!
	// 当是同名函数, 但此函数不是虚的,继承类中的此函数也能覆盖基类函数;
	// 但也保存了基类的此函数的指针;
	// 当强转为基类指针时,发生了指针回到基类的情况。

	system("pause");
	return 0;
}


 

运行结果是:

可以看出:

(1) 因为是虚函数,所以继承类中只存了此同名函数中自己的地址,基类地址被抛.

(2)/当是同名函数, 但此函数不是虚的,继承类中的此函数也能覆盖基类函数;
        但也保存了基类的此函数的指针;
        当强转为基类指针时,发生了指针回到基类的情况。

10 、有两个无关的类,但它们的类中有同名的成员函数,当一个类的指针指向另一类引起的virtual地址替换?
//=============== 10、有两个无关的类,但它们的类中有同名的成员函数。
//====================当一个类的指针指向另一类引起的virtual地址替换?
#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	virtual void show() const {  std::cout << "show A..." << std::endl; }
	void show_noVirtual() const {  std::cout << "show_noVirtual A..." << std::endl; }

};

class B 
{
public:
	virtual void show() const {  std::cout << "show B..." << std::endl; }
	void show_noVirtual() const {  std::cout << "show_noVirtual B..." << std::endl; }
};


int main (int argc, const char * argv[])
{
	A *pa_Degret = NULL;

	B b;
	pa_Degret = (A*)&b;


	// 总结:
	// 可以看出与第9点有继承关系的类发生的情况是一致的:
	// 1、当是同名函数, 但此函数是虚的,B类中的此函数能覆盖A函数;
	// 但没保存A类的此函数的指针;
	// 当强转为A类指针时,发生了指针回不到A类的情况
	// 2、当是同名函数, 但此函数不是虚的,B类中的此函数也能覆盖A函数;
	// 但也保存了A类的此函数的指针;
	// 当强转为A类指针时,发生了指针回到A类的情况
	pa_Degret->show();
	pa_Degret->show_noVirtual();



	system("pause");

	return 0;
}


运行结果:

可以看出:

(1)、当是同名函数, 但此函数是虚的,B类中的此函数能覆盖A函数; 但没保存A类的此函数的指针;当强转为A类指针时,发生了指针回不到A类的情况
(2)、当是同名函数, 但此函数不是虚的,B类中的此函数也能覆盖A函数;但也保存了A类的此函数的指针;当强转为A类指针时,发生了指针回到A类的情况

11、如果以一个基类指针指向一个派生对象,那么经由此指针,只能调用基础类别所定义的函数 
//=============== 10、如果以一个基类指针指向一个派生对象,那么经由此指针,
//                   只能调用基础类别所定义的函数===========================
#include <iostream>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class A
{
public:
	void show_A() const {  std::cout << "show_A A..." << std::endl; }
	// 类A中增加了重载赋值运算符的函数,且内部将对象首地址的虚表指针更改
	A & operator=(const A& a)
	{
		if(this != &a)
		{
			*(int *)this = *(int *)&a;
		}
		return *this;
	}
};

class A_Ex : public A
{
public:
	void show_A_Ex() const { std::cout << "show_A_Ex A_Ex..." << std::endl; }
};


int main (int argc, const char * argv[])
{
	A *pa_Degret = NULL;
	A_Ex *pa_Ex_Degret = NULL;

	A_Ex a_Ex;
	pa_Degret = (A*)&a_Ex;
	pa_Ex_Degret = (A_Ex *)&a_Ex;

	// 情况一
	//pa_Degret->show_A_Ex();		//  编译不通过
	pa_Ex_Degret->show_A_Ex();	// 正确,调用A_Ex::show_A_Ex 函数。


	system("pause");

	return 0;
}


 

运行结果是:

 

可以看出:

C++类内部存储分析(含源码)(原创+转载)_第6张图片

 

 

你可能感兴趣的:(C++,delete,System,存储,Class,编译器)