首先我得说,我的大学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++,能力有限,如有错误,还请大家多多谅解并指正出来,同时希望大家可以一起交流学习。