当前位置:文档之家› 分析c++对象在内存中的布局情况

分析c++对象在内存中的布局情况

分析c++对象在内存中的布局情况

liuxuezong

2013.05.10

前言

微软的VS2010提供了一个新的选项,给用户显示C++对象在内存中的布局。这个选项就是:/d1reportSingleClassLayout。

具体使用方法如下,在写好相应的cpp文件之后,需要启动VS2010的命令行工具Visual Studio 命令提示(2010) 即“Visual Studio 2010Command Prompt”,切换到cpp文件所在目录之后,输入如下的命令:

cl [filename].cpp /d1reportSingleClassLayout[className]

或者

把结果输出到filename.cl文件中:

cl [filename].cpp /d1reportSingleClassLayout[className] >filename.cl

或者在工程属性命令加上(工程属性->配置属性->C/C++->命令行->其他选项):

/d1reportSingleClassLayout[className]

直接编译输出对象内存布局结果。

cl当然就是MS的编译器;[filename].cpp就是你所想要查看的class所在的cpp文件(class定义在头文件也没关系,还是只要编译cpp文件即可);而你需要在最后加上[className],也就是你需要查看的class的类名。

内存对齐问题

本文在分析C++对象在内存的布局之前,我们简单地讲述一下内存对齐关键性问题。无论是面向过程编程,还是面向对象编程,不同的结构对齐方式(1,2,4,8,16)会影响内存的布局。下面的内容默认以8个字节对齐,编码方式是32位的X86系统即小端,编译环境为VS2010-Debug,需要注意VS2010与VC6的Debug内存分配有所不同,release两者一致。

为何要内存对齐?

https://www.doczj.com/doc/859797075.html,/developerworks/library/pa-dalign/

因为处理器读写数据,并不是以字节为单位,而是以块(2,4,8,16字节)为单位进行的。如果不进行对齐,那么本来只需要一次进行的访问,可能需要好几次才能完成,并且还要进行额外的合并或者数据分离。导致效率低下。更严重地,会因为cpu不允许访问unaligned address,就会报错,或者打开调试器或者dump core,比如sun sparc solaris绝对不会容忍你访问unaligned address,都会以一个core结束你的程序的执行。所以一般编译器都会在编译时做相应的优化以保证程序运行时所有数据都是存储在'aligned address'上的,这就是内存对齐的由来。

在'Data alignment: Straighten up and fly right'这篇文章中作者还得出一个结论那就是:"如果访问的地址是unaligned的,那么采用大粒度访问内存有可能比小粒度访问内存还要慢"。

?对齐规则

结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项。结构体内部成员的对齐要求,结构体本身的对齐要求。最重要的有三点:

1)成员对齐。对于结构体内部成员,通常会有这样的规定:各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。但是也可以看到,有时候某些字段如果严格按照大小紧密排列,根本无法达到这样的目的,因此有时候必须进行padding。各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节编译器会自动填充也就是padding。

2)然后,还要考虑整个结构体的对齐需求。ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格。实际上要求结构体至少是其中的那个最大的元素大小的整数倍。因为有时候我们使用的是结构体数组,所以结构体的大小还得保证结构体数组中各个结构体满足对齐要求,同时独立的结构体与结构体数组中单个结构体的大小应当是一致的。

3)编译器的对齐指令。VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n 字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数。

●代码段

class CSample

{

public:

CSample() : a('1'), b(2), c(3), d(4.01f) {}

~CSample() {}

public:

char a;

int b;

short c;

double d;

};

int main(int argc, char* argv[])

{

int m = 0;

printf("&m=[%08x]\n", &m);

CSample s;

printf("sizeof(CSample)=%d\n", sizeof(CSample));

printf("&a=[%08x]\n", &(s).a);

printf("&b=[%08x]\n", &(s).b);

printf("&c=[%08x]\n", &(s).c);

printf("&d=[%08x]\n", &(s).d);

int n = 0;

printf("&n=[%08x]\n", &n);

return 0;

}

1)结构体按8个字节对齐情形:

1> class CSample size(24):

1> +---

1> 0 | a

1> | (size=3) // 编译器填充3个字节,以保持内存对齐。

1> 4 | b

1> 8 | c

1> | (size=6) // 编译器填充6个字节,以保持内存对齐。

1> 16 | d

1>+---

内存段

控制台输出结果(VS2010 Debug):

&m=[0035f9b8] // relase应该是0035f9b0

sizeof(CSample)=24

&a=[0035f998]

&b=[0035f99c]

&c=[0035f9a0]

&d=[0035f9a8]

&n=[0035f98c] // relase应该是0035f994

过程说明n=8:

char a------偏移量为0,满足对齐方式,a 占用1个字节;

int b -------下一个可用的地址的偏移量为1,不是min(sizeof(int), n)=4的倍数,需要补足3 个 字节才能使偏移量变为4(满足对齐方式),因此VC 自动填充3个字节,b 存放 在偏移量为4的地址上,它占用4个字节。

short c-----下一个可用的地址的偏移量为8,是min(sizeof(short), n)=2的倍数,满足short 的 对齐方式,所以不需要VC 自动填充,c 存放在偏移量为8的地址上,它占用2 个字节。

double d--下一个可用的地址的偏移量为10,不是min(sizeof(double), n)=8的倍数,需要补足 6字节才能使偏移量变为16(满足对齐方式),因此VC 自动填充6个字节,d 存 放在偏移量为16的地址上,它占用8个字节。

2)结构体按4个字节对齐情形:

1> class CSample size(20):

1> +--- 1> 0 | a

1> | (size=3) // 编译器填充3个字节,以保持内存对齐。 1> 4 | b 1> 8 | c

1> | (size=2) // 编译器填充2个字节,以保持内存对齐。 1> 12 | d

1> +---

内存段

n=0035f98c

a=0035f998 d=0035f9a8 m=0035f9b8

c=0035f9a0 b=0035f99c

控制台输出结果(VS2010 Debug): &m=[0016f7d4] // relase 应该是0035f9cc sizeof(CSample)=20 &a=[0016f7b8] &b=[0016f7bc] &c=[0016f7c0] &d=[0016f7c4]

&n=[0016f7ac] // relase 应该是0035f9b4

过程说明n=4:

char a------偏移量为0,满足对齐方式,a 占用1个字节;

int b -------下一个可用的地址的偏移量为1,不是min(sizeof(int), n)=4的倍数,需要补足3 个 字节才能使偏移量变为4(满足对齐方式),因此VC 自动填充3个字节,b 存放 在偏移量为4的地址上,它占用4个字节。

short c-----下一个可用的地址的偏移量为8,是min(sizeof(short), n)=2的倍数,满足short 的 对齐方式,所以不需要VC 自动填充,c 存放在偏移量为8的地址上,它占用2 个字节。

double d--下一个可用的地址的偏移量为10,不是min(sizeof(double), n)=4的倍数,需要补足 2字节才能使偏移量变为12(满足对齐方式),因此VC 自动填充2个字节,d 存 放在偏移量为12的地址上,它占用8个字节。

n=0x0016f7ac a=0x0016f7b8 d=0x0016f7c4 m=0x0016f7d4

c=0x0016f7c0 b=0x0016f7bc

1 最简单的类

●代码段

class CSample

{

public:

CSample() : n(1) {}

~CSample() {}

public:

void f1() {}

private:

int n;

};

int main(int argc, char* argv[])

{

CSample *p = new CSample;

delete p;

p = NULL;

return 0;

}

图1-1 类模型

●分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CSample size(4):

+---

0 | n

+---

Microsoft (R) Incremental Linker Version 10.00.30319.01

Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

●内存段

●小结

该类可以看出其总长度为各个成员的长度之和,变量位置按申明的先后顺序,地址的值从底到高,普通成员函数不占空间。

2 虚函数的类(单个虚函数)

●代码段

class CSample

{

public:

CSample() : n(1) {}

virtual ~CSample() {}

public:

void f1() {}

private:

int n;

};

int main(int argc, char* argv[])

{

CSample *p = new CSample;

delete p;

p = NULL;

return 0;

}

图2-1 类模型

分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CSample size(8):

+---

0 | {vfptr}

4 | n

+---

CSample::$vftable@:

| &CSample_meta

| 0

0 | &CSample::{dtor}

CSample::{dtor} this adjustor: 0 // destructor

CSample::__delDtor this adjustor: 0 // deleting destructor

CSample::__vecDelDtor this adjustor: 0 //vector deleting destructor

Microsoft (R) Incremental Linker Version 10.00.30319.01

Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

说明:

如果必要的话, 析构函数声明为virtual。如果你的类有虚函数, 则析构函数也应该为虚函数。当重载一个虚函数,在继承类中把它明确地声明为virtual。理论依据:如果省略virtual 关键字,代码阅读者不得不检查所有父类,以判断该函数是否是虚函数。

adjustor表示虚函数机制执行时,this指针的调整量,假如~ CSample()被多态调用的话,那么它的形式如下:

*(this+0)[0]()

总结虚函数调用形式,应该是:

*(this指针+调整量)[虚函数在vftable内的偏移]()

参考说明

Visual C++ extends its “layered destructor model” to automatically create another hidden destructor helper function, the “deleting destructor,” whose address replaces that of the “real” virtual destructor in the virtual function table. This function calls the destructor appropriate for the class, then optionally invokes the appropriate operator delete for the class.

简言之,是用来保证delete的时候能调用到派生类自定义的operator delete。

●内存段

●小结

1)该类的总长度为成员的长度加上虚函数表指针的长度,成员函数不占空间;

2)虚函数表指针总是出现在内存的首地址,虚函数表指针占用4个字节。

3 虚函数的类(多个虚函数)

●代码段

class CSample

{

public:

CSample() : n(1) {}

virtual ~CSample() {}

public:

virtual void f1() {}

void f2() {}

private:

int n;

};

int main(int argc, char* argv[])

{

CSample *p = new CSample;

delete p;

p = NULL;

return 0;

}

图3-1 类模型

●分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CSample size(8):

+---

0 | {vfptr}

4 | n

+---

CSample::$vftable@:

| &CSample_meta

| 0

0 | &CSample::{dtor}

1 | &CSample::f1

CSample::{dtor} this adjustor: 0

CSample::f1 this adjustor: 0

CSample::__delDtor this adjustor: 0

CSample::__vecDelDtor this adjustor: 0

Microsoft (R) Incremental Linker Version 10.00.30319.01

Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

●内存段

●小结

1)该类的总长度为成员的长度加上虚函数表指针的长度,成员函数不占空间;

2)虚函数表指针总是出现在内存的首地址,虚函数表指针占用4个字节;

3)所有的虚函数都共用同一个虚函数表。

4 简单的继承类

●代码段

class CBase

{

public:

CBase() : n(1) {}

~CBase() {}

public:

void f1() {};

private:

int n;

};

class CDerived : public CBase

{

public:

CDerived() : d(2) {}

~CDerived() {}

public:

void f2() {};

private:

int d;

};

int main(int argc, char* argv[])

{

CDerived *p = new CDerived;

delete p;

p = NULL;

return 0;

}

图3-1 类模型

●分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CDerived size(8):

+---

| +--- (base class CBase)

0 | | n

| +---

4 | d

+---

Microsoft (R) Incremental Linker Version 10.00.30319.01

Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

●内存段

●小结

1)该类的总长度为成员的长度加上虚函数表的长度,成员函数不占空间;

2)虚函数表指针总是出现在内存的首地址,虚函数表指针占用4个字节;

3)该内存的布局顺序总是父类成员变量n->继承类成员变量d。

5 简单的继承类(单个虚函数)

代码段

class CBase

{

public:

CBase() : n(1) {}

~CBase() {}

public:

virtual void f1() {};

private:

int n;

};

class CDerived : public CBase

{

public:

CDerived() : d(2) {}

~CDerived() {}

public:

void f2() {};

private:

int d;

};

int main(int argc, char* argv[])

{

CDerived *p = new CDerived;

delete p;

p = NULL;

return 0;

}

图5-1 类模型

分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CDerived size(12):

+---

| +--- (base class CBase)

0 | | {vfptr}

4 | | n

| +---

8 | d

+---

CDerived::$vftable@:

| &CDerived_meta

| 0

0 | &CBase::f1

Microsoft (R) Incremental Linker Version 10.00.30319.01

Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

●小结

该内存的布局顺序总是虚函数指针变量->父类成员变量n->继承类成员变量d。

6 简单的继承类(覆盖基类虚函数)

●代码段

class CBase

{

public:

CBase() : n(1) {}

~CBase() {}

public:

virtual void f1() {};

private:

int n;

};

class CDerived : public CBase

{

public:

CDerived() : d(2) {}

~CDerived() {}

public:

virtual void f1() {};

void f2() {};

private:

int d;

};

int main(int argc, char* argv[])

{

CDerived *p = new CDerived;

delete p;

return 0;

}

图6-1 类模型

分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CDerived size(12):

+---

| +--- (base class CBase)

0 | | {vfptr}

4 | | n

| +---

8 | d

+---

CDerived::$vftable@:

| &CDerived_meta

| 0

0 | &CDerived::f1

CDerived::f1 this adjustor: 0

Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

●内存段

●小结

7 简单的继承类(基类析构虚函数)

●代码段

class CBase

{

public:

CBase() : n(1) {}

virtual ~CBase() {}

public:

virtual void f1() {};

private:

int n;

};

class CDerived : public CBase

{

public:

CDerived() : d(2) {}

~CDerived() {}

public:

void f1() {};

void f2() {};

private:

int d;

};

int main(int argc, char* argv[])

{

CDerived *p = new CDerived;

delete p;

p = NULL;

return 0;

}

图7-1 类模型

分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CDerived size(12):

+---

| +--- (base class CBase)

0 | | {vfptr}

4 | | n

| +---

8 | d

+---

CDerived::$vftable@:

| &CDerived_meta

| 0

0 | &CDerived::{dtor}

1 | &CDerived::f1

CDerived::{dtor} this adjustor: 0

CDerived::f1 this adjustor: 0

CDerived::__delDtor this adjustor: 0

CDerived::__vecDelDtor this adjustor: 0

Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe

test.obj

●内存段

●小结

8 简单的继承类(多个虚函数)

●代码段

class CBase

{

public:

CBase() : n(1) {}

virtual ~CBase() {}

public:

virtual void f1() {};

private:

int n;

};

class CDerived : public CBase

{

public:

CDerived() : d(2) {}

virtual ~CDerived() {}

public:

void f1() {};

virtual void f2() {};

private:

int d;

};

int main(int argc, char* argv[])

{

CDerived *p = new CDerived;

delete p;

p = NULL;

return 0;

}

图8-1 类模型

分析内存布局

test.cpp

d:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc class CDerived size(12):

+---

| +--- (base class CBase)

0 | | {vfptr}

4 | | n

相关主题
文本预览
相关文档 最新文档