关于内存对齐问题

a.基本类型:所有的基本类型都有相应的对齐参数,编译器在编译时,会用全局的对齐参数和当前类型的对齐参数中较小的一个进行对齐。比如,编译时指定暗8bytes对齐(#pragma pack(8)实现之),可是由于一个char变量的大小为一个byte,所以最后还是按1byte对齐。

b.复合类型:复合类型的对齐原则,就是取其成员变量数据类型的字节数的最大者和在编译时指定的对齐数两者之间较小的字节数进行对齐如果没有用诸如#pragma pack指定全局对齐数,则该复合类型的对齐数就是其成员变量数据类型字节数之最大者。

#include <iostream>

using namespace std;

// #pragma pack(4)

class PatClass0    // PatClass0的对齐数为1。因为其成员变量c1的数据类型为char,而sizeof(char)=1

{

private:

char c1;

};

class PatClass1    // PatClass1的对齐数为1。因为其成员变量c1c2的数据类型均为char,而sizeof(char)=1

{

private:

char c1;

char c2;

};

class PatClass2    // PatClass2的对齐数为2。因为其成员变量c1的数据类型均为charsizeof(char)=1

{                //成员变量c2的数据类型为short,而sizeof(short)=2

private:            //取其字节数最大者,即2

char c1;

short c2;

};

class PatClass3    // PatClass3的对齐数为4。因为其成员变量c1的数据类型均为charsizeof(char)=1

{                //成员变量c2的数据类型为int,而sizeof(int)=4

private:            //取其字节数最大者,即4

char c1;

int c2;

};

class PatClass4    // PatClass4的对齐数为8。因为其成员变量c1的数据类型均为charsizeof(char)=1

{                //成员变量c2的数据类型为double,而sizeof(8)=8

private:            //取其字节数最大者,即8

char c1;

double c2;

};

class PatClass5    // PatClass5的对齐数为4。见PatClass3说明

{

private:

int c1;

};

class PatClass6 : publicPatClass5    // PatClass6的对齐数为4。这是因为它继承了PatClass5PatClass5中的成员

{                            //变量c1也会被编译器安插在类PatClass6的对象中(尽管不能对其进行普通意

private:                        //义上的访问),而c1的数据类型为intsizeof(int)=4。所以PatClass6的对齐数

char c2;                    //4

};

int main(void)

{

PatClass0 c0;

PatClass1 c1;

PatClass2 c2;

PatClass3 c3;

PatClass4 c4;

PatClass5 c5;

PatClass6 c6;

cout << "the size of PatClass0 object is:" << sizeof(c0) << endl;

// PatClass0的对齐数为1byte,它只有一个成员变量char c1,因此其对象c0的大小为1byte

cout << "the size of PatClass1 object is:" << sizeof(c1) << endl;

// PatClass1的对齐数为1byte,它有两个成员变量char c1char c2,因此其对象c1的大小为2bytes

cout << "the size of PatClass2 object is:" << sizeof(c2) << endl;

// PatClass2的对齐数为2bytes,它有一个成员变量char c1和一个成员变量short c2,合起来为3bytes,由于其

//对齐数为2bytes,因此需要填充1byte,才能变成2的倍数。故此其对象c2的大小为4bytes

cout << "the size of PatClass3 object is:" << sizeof(c3) << endl;

// PatClass3的对齐数为4bytes,它有一个成员变量char c1和一个成员变量int c2,合起来为5bytes,由于其

//对齐数为4bytes,因此需要填充3bytes,才能变成4的倍数。故此其对象c3的大小为8bytes

cout << "the size of PatClass4 object is:" << sizeof(c4) << endl;

// PatClass4的对齐数为8bytes,它有一个成员变量char c1和一个成员变量double c2,合起来为9 bytes,由于

//其对齐数为8bytes,因此需要填充7bytes,才能变成8的倍数。故此其对象c4的大小为16 bytes

cout << "the size of PatClass5 object is:" << sizeof(c5) << endl;

// PatClass5的对齐数为4 bytes,它只有一个成员变量int c1,合起来为4bytes,刚好是其对齐数的倍数。

//故此其对象c5的大小为4 bytes

cout << "the size of PatClass6 object is:" << sizeof(c6) << endl;

// PatClass6的对齐数为4 bytes,它有一个成员变量char c2,并从PatClass5中继承来了int c1,合起来为5bytes

//因此需要填充3bytes,才能变成4的倍数。故此其对象c6的大小为8 bytes

return 0;

}

输出结果为:

如果用#pragma pack(4)(在上面的程序中被注释了)指定全局对齐数为4bytes,结果如下:

其实这样的指定,只影响到了PatClass4,因为只有它有一个double成员变量,而sizeof(double)=8bytes,其他各类

均没有数据类型超过4bytes的。在前面曾经提及,一个类对象的内存对齐数 = min(指定的全局对齐数,成员变量数据

类型字节数最大者),因此在这个例子中PatClass4类型的对象的对齐数应该为4bytes。在PatClass类型的对象c4中,

成员变量double c28bytes,另外一个成员变量charc11byte,合起来一共9 bytes,因此要最小填充3 bytes

才能成员对齐数4的倍数,即此时PatClass4类型的对象的大小为12bytes




一个类的对象到底有多大?其大小由什么因素影响?

我们假定这个类没有继承任何其他类,且没有虚函数。先看下面例子:

         #include <iostream>

using namespace std;

 

class Concrete

{

public:

         Concrete():val(0), c1('A'), c2('B')//, c3('C')

         {

         }

 

private:

         int val;

         char c1;

         char c2;

         //char c3;

};

 

int main(void)

{

         Concrete c;

         cout << sizeof(c) << endl;

 

         return 0;

}

运行上面的程序,不管是否有成员变量c3,输出结果均为8,为什么呢?

由于内存的alignment的原因,所有对象的大小为4bytes的整数倍(因为其数据成员的数据类型最大占用字节数为4,即sizeof(int val) = 4),不足的就填充,如下面的图示。

         关于内存对齐问题(二) - 玄机逸士 - 玄机逸士博客   关于内存对齐问题(二) - 玄机逸士 - 玄机逸士博客                              关于内存对齐问题(二) - 玄机逸士 - 玄机逸士博客

                       (有成员变量c3的情况)                               (没有成员变量c3的情况)

 

出现在derived class中的base class subobject保持其完成原样性

请看下面例子:

#include <iostream>

using namespace std;

 

class Concrete1

{

private:

         int val;

         char c1;

public:

         inline Concrete1(int i, char c1) : val(i), c1(c1)

         {

         }

};

 

class Concrete2 : public Concrete1

{

private:

         char c2;

public:

         inline Concrete2(int i, char c1, char c2) : Concrete1(i, c1), c2(c2)

         {

         }

};

 

class Concrete3 : public Concrete2

{

private:

         char c3;

public:

         inline Concrete3(int i, char c1, char c2, char c3) : Concrete2(i, c1, c2), c3(c3)

         {

         }

};

 

int main(void)

{

         Concrete3 c(1, 'A', 'B', 'C');

         cout << sizeof(c) << endl;

 

         return 0;

}

输出会是多少呢?类Concrete3和1)中的类Concrete完成的功能一模一样,但是Concrete3的对象的空间确变成了16bytes,比原来的整整多出一倍!原因就是C++语言保证“出现在derived class中的base class suboject有其完整性”。图解如下:

关于内存对齐问题(二) - 玄机逸士 - 玄机逸士博客

在上图中Concrete2类型的对象中,为什么也要填充3bytes呢?这还是因为继承了Concrete1的缘故。正是因为继承了Concrete1中的int val,因此Concrete2的对齐数变成了4 bytes,因此必须要填充3 bytes。总结如下:

其一,C++语言保证“出现在derived class中的base class suboject有其完整性”;

其二,derived class的对齐数 = min(指定的全局对齐数,max(base class的对齐数,derived class的对齐数))



第一种情况:

struct BBB

{

                  long num;                // 4bytes

                  char *name;            // 4 bytes

                  short int data;          // 2 bytes

                  char ha;                    // 1 byte

                  short ba[5];              // 10 bytes

};

sizeof(BBB) = 24bytes

理由:

1. 很容易知道BBB的内存对齐数是4bytes

2. num和name各为4bytes,已经对齐

3. data和ha加起来是3bytes,因此要补1byte

4. ba共10bytes,因此要补2bytes

 

第二种情况:

struct BBB

{

                  long num;                 // 4 bytes

                  char *name;             // 4 bytes

                  short int data;           // 2 bytes

                  char ha;                     // 1 byte

                  char hb;                     // 1 byte

                  short ba[5];                // 10 bytes

};

sizeof(BBB) = 24bytes

理由:

1. 很容易知道BBB的内存对齐数是4bytes

2. num和name各为4bytes,已经对齐

3. data、ha和hb加起来是4bytes,已经对齐

3. ba共10bytes,因此要补2bytes

 

第三种情况:

struct BBB

{

                  char hb;                 // 1 byte

                  long num;             // 4 bytes

                  char *name;         // 4 bytes

                  short int data;       // 2 bytes

                  char ha;                 // 1 byte

                  short ba[5];           // 10 bytes

};

sizeof(BBB) = 28bytes

理由:

1. 很容易知道BBB的内存对齐数是4bytes

2. hb为1byte,因此需要补3bytes

3. num和name各为4bytes,已经对齐

4. data、ha加起来是3bytes,因此要补1byte

5. ba共10bytes,因此要补2bytes

 

通过上述三种情况,我们可以得出推论:

a. 尽管成员变量一样,如果由于排列的顺序不同,则所得到对象的大小也有可能不同

b. 相同数据类型的成员变量,在结构或类定义时,尽量相邻,这样可以减少空间的消耗

 

下面再举一个例子,来说明上述推论b:

假定结构BBB定义如下:

struct BBB

{

                  char          ha;

                  int             a;

                  char          hb;

                  int             b;

                  char          hc;

                  int             c;

};

那么sizeof(BBB) = 24bytes

 

如果结构BBB的定义改为:

struct BBB

{

                  char          ha;

                  char          hb;

                  char          hc;

                  int             a;

                  int             b;

                  int             c;

};

那么sizeof(BBB) = 16bytes

可见在两种情况下结构BBB所能承载的数据量是一样的,但所占用的空间却有很大的不同。

 

顺便简单复习一下数据类型自身对齐值方面的问题。char类型的对齐数为1byte,short类型为2bytes,int、float和double类型,均为4bytes。由于数据类型有自身对齐值,因此,short类型的变量的起始地址必须为2的倍数,int、float和double类型的变量的起始地址必须为4的倍数。char类型的对齐数为1,所以char类型变量的起始地址,可以在任何有效的位置。请参考下面的代码:

#include <iostream>

using namespace std;

struct foo1

{

    char c1;      // 0            

    short s;      // 2 ~ 3       s为short类型,因此其起始地址必须是2的倍数

    char c2;     // 4

    int i;            // 8 ~ 11    i为int类型,因此其起始地址必须是4的倍数

};

 

struct foo2

{

    char c1;      // 0

    char c2;      // 1

    short s;       // 2 ~ 3

    int i;             // 4 ~ 7

};

 

int main()

{

    cout << sizeof(foo1) << endl;      // 12

    cout << sizeof(foo2) << endl;      // 8

 

    return 0;

}

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值