当前位置:C++技术网 > 资讯 > 关于成员对齐方式

关于成员对齐方式

更新时间:2015-06-23 23:37:58浏览次数:1+次

    首先我得说,我的大学C语言老师告诉我,结构体的大小是所有数据成员的大小加起来。。我忘了我有没有验证过,不过真是误人子弟啊!!
    结构体或类的数据成员的类型,声明顺序,采用的对齐方式等都会影响对象的实际大小和访问效率。
    下面我给出了林先森的例子,大家有兴趣的可以粘到自己的环境下学习一下。在例子中我会给出我的心得。

//sedan.h
#ifndef _SEDAN_H_
#define _SEDAN_H_

enum Color
{
    RED = 0x01,BLUE,GREEN,YELLOW,BLACK
};

typedef unsigned char BYTE;
//完全混乱式的排列数据成员
struct Sedan
{
    bool m_hasSkylight;
    Color m_color;
    bool m_isAutoShift;
    double m_price;
    BYTE m_seatNum;
};

#ifdef _cpluslus
extern "C"
{
    #endif
    void _cdecl print_Sedan_1(const Sedan *p);
    #ifdef _cplusplus
}
#endif

#endif //_SEDAN_H_

//在这里我要说的是结构体和类实质上几乎没有区别,原因请查阅百度。

//sedan.cpp
#include "sedan.h"
#include <iostream>

using namespace std;
#pragma pack(push,8)
#ifdef _cplusplus
extern "C"
{
    #endif
    void _cdecl print_Sedan_1(const Sedan *p)
    {
        cout<<"print_Sdean_1() in library PrintSedan:" << endl;
        cout<<"//Sedan:\n";
        cout<<"\tm_has_Skylight = " << (p->m_hasSkylight?"yes":"no") << endl;
        cout<<"\tm_color = " << p->m_color << endl;
        cout<<"\tm_isAutoShift = " << (p->m_isAutoShift?"yes":"no") << endl;
        cout<<"\tm_price = " << p->m_price << endl;
        cout<<"\tm_seatNum = " << (int)(p->m_seatNum) << endl;
        cout<<"\n\n";
    }
    #ifdef _cpluscplus
}
#endif
#pragma pack(pop)

//main.cpp
#include "sedan.h"
#include <iostream>
using namespace std;
#pragma pack(push,4)
void _cdecl sedan_offsetof();
void _cdecl print_Sedan_2(const Sedan *p);

void _cdecl print_Sedan_2(const Sedan *p)
{
    cout << "print_Sdean_2() in main.cpp:" << endl;
    cout << "//Sedan:\n";
    cout << "\tm_has_Skylight = " << (p->m_hasSkylight?"yes":"no") << endl;
    cout << "\tm_color = " << p->m_color << endl;
    cout << "\tm_isAutoShift = " << (p->m_isAutoShift?"yes":"no") << endl;
    cout << "\tm_price = " << p->m_price << endl;
    cout << "\tm_seatNum = " << (int)(p->m_seatNum) << endl;
    sedan_offsetof();
    cout << "\n\n";
}
#pragma pack(pop)

//计算Sedan每个数据成员的偏移字节
void _cdecl sedan_offsetof()
{
    cout << "offsetof(Sedan,m_hasSkylight) = " << offsetof(Sedan,m_hasSkylight) << endl; // BOOL
    cout << "offsetof(Sedan,m_color) = " << offsetof(Sedan,m_color) << endl;             // ENUM
    cout << "offsetof(Sedan,m_isAutoShift) = " << offsetof(Sedan,m_isAutoShift) << endl; // BOOL
    cout << "offsetof(Sedan,m_price) = " << offsetof(Sedan,m_price) << endl;             // DOUBLE
    cout << "offsetof(Sedan,m_seatNum) = " << offsetof(Sedan,m_seatNum) << endl;         // UNSIGNED CHAR
}
int main()
{
    Sedan s;
    s.m_hasSkylight = true;
    s.m_color = GREEN;
    s.m_isAutoShift = false;
    s.m_price = 100000;
    s.m_seatNum = 4;
    print_Sedan_2(&s);
    print_Sedan_1(&s);
    sedan_offsetof();
    cout << "sizeof(s) = " << sizeof(s) << endl;
    system("pause");
    return 0;
}

//在VS2012环境下的结果是:
print_Sdean_2() in main.cpp:

    //Sedan:
    m_has_Skylight = yes
    m_color = 3
    m_isAutoShift = no
    m_price = 100000
    m_seatNum = 4

    //第一次是按照4字节对齐方式
    offsetof(Sedan,m_hasSkylight) = 0 // 第一个数据成员的偏移量为0,BOOl类型为1个字节
    offsetof(Sedan,m_color) = 4       // 第二个数据竟然偏移了4个字节,也就是说有3个字节的内存被白白浪费了 ENUM 为四个字节
    offsetof(Sedan,m_isAutoShift) = 8 // 没有浪费  
    offsetof(Sedan,m_price) = 16      // 浪费了7个字节 DOUBLE类型的数据为8个字符
    offsetof(Sedan,m_seatNum) = 24    // 浪费了7个字节 UNSIGNED CHAR
        
      //浪费的字节全部被编译器填充。

      //也就是说浪费了17个字节,而总共只有32个字节,可见浪费有多严重。

    //第二次是按照8字节对齐方式,按照书上的例子,应该出错,但是VS编译器进行了特殊处理,所以就和上面这个情况一样了。

print_Sdean_1() in library PrintSedan:

    //Sedan:
    m_has_Skylight = yes
    m_color = 3
    m_isAutoShift = no
    m_price = 100000
    m_seatNum = 4

    offsetof(Sedan,m_hasSkylight) = 0 // BOOL
    offsetof(Sedan,m_color) = 4       // ENUM
    offsetof(Sedan,m_isAutoShift) = 8 // BOOL
    offsetof(Sedan,m_price) = 16      // DOUBLE
    offsetof(Sedan,m_seatNum) = 24    // UNSIGNED CHAR
    sizeof(s) = 32
     总结:
    对于复合类型的数据成员的安排,我们一定要仔细,按照“从大到小”排列,原因是,乱序排的话,编译器需要填充的空间就越多,如果从小到大排的话,填充的空间就极有可能在中间,这些空间就很可能随着对复合类型对齐方式的调整而被压缩甚至吸收,从而导致部分数据成员的偏移发生改变(成员前移)。此时就可能出现数据改变,程序崩溃的危险。
    按照“从大到小”排列的优点是,我们可以吸收末尾的填充字节,扩大相应的数据成员的范围。而且这样可以充分利用了内存,提高了效率。

    由于本人也是初学C++,能力有限,如有错误,还请大家多多谅解并指正出来,同时希望大家可以一起交流学习。