[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2]
想知道一个类内部是什么,没有比知道它存储的数据还更直接的了。
Q: 为什么一个不包含任何成员变量的类的大小不是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; } }; int main (int argc, const char * argv[]) { COUT_ENDL(sizeof(Test)) return 0; }
运行结果:
sizeof(Test) is 1
Q: Test类的构造函数占用类对象的空间吗?
A: 根据上面sizeof得到的值,它应该是不占用的。从实际上说,从代码段的角度,它们和c函数中的全局函数没什么不同;只不过如何访问它们可能有限制而已。经过测试,发现如果想直接获取Test类构造函数的地址,赋值给c风格函数指针,编译器总是报错,必须采用指向类成员函数指针的方式才能使用,所以这里无法给出一个很好证明它类似全局函数的例子。
Q: 如果一个类中含有成员变量,可以通过获取此类对象的地址绕开访问权限函数来修改对象内部数据吗?
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()) return 0; }
a.value_one() is 10 a.value_two() is 100 a.value_one() is 11 a.value_two() is 111
Q: 如果具备继承体系的类对象,成员如何摆放?
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)) return 0; }
*(int *)pa is 10 *((int *)pa + 1) is 100 *((int *)pa + 2) is 1000
Q: 如果类中含有虚函数,类大小会发生怎样的变化?
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)) return 0; }
运行结果(生成32位应用程序):
sizeof(A) is 12 sizeof(A_Ex) is 16
很明显,可以看到,虚表指针占用了对象最初的部分,后面接着是成员变量。
Q: 虚表指针到底在哪里?
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; // get the address of a, also the vtable pointer's pointer COUT_ENDL(p_vtable) int *vtable = (int *)*p_vtable; // dereference to get the vtable pointer COUT_ENDL(vtable) // get the first virtual func addr int *firstVirtualFunc = (int *)vtable[0]; COUT_ENDL(firstVirtualFunc) // convert it to the c_style func, and call it c_style_show show_func = (c_style_show)firstVirtualFunc; show_func(); return 0; }
对象a虚表指针以及虚表内部函数指针的关系简图如下:
由上图,可知,show_func将被赋值为A类中的show函数。执行结果如下:
p_vtable is 0xbffff9d8 vtable is 0x2060 firstVirtualFunc is 0x1b50 show A...
其实,从上面的分析,也能间接证明类中的成员函数也可以看成全局函数的特殊形式,只是访问方式有所限制。
Q: 当子类和父类的大小不一致的时候,出现的对象切割,该如何理解?
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; }; 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; return 0; }
show A... show A_Ex... show A... show A_Ex...
在两个断点处打印*pa的数据:
可以看出*pa数据确实被对象a_ex的数据替换了,不过也可以看出对象a的虚表指针依然没变,导致了调用show函数没有改变。
Q: 可以改变对象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 & 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; return 0; }
运行结果:
show A... show A_Ex... show A_Ex... show A_Ex...
如此简单直白的对象模型, c++继承了c语言的特点。
xichen
2012-6-2 11:05:41