高质量程序设计指南c++/c语言(10)--位域和自然对齐
处于对齐的考虑(将对象的大小调整到机器字的整数倍),每个对象的存储空间中可能会存在填充字节,这些字节单元不会初始化,而是具有上次使用留下来的随机值。显然,每个对象填补字节的内容是不会相同的。这就是说,如果编译器支持使用逐位比较的默认方法来比较同类型对象,结果肯定是不对的。
1、位域
c语言位域各成员的类型必须是int\unsigned int\signed int等类型,c++还允许使用long、char。但是不允许使用指针类型或者浮点类型作为位域的成员类型。
struct Student
{
unsigned int day:5;
unsigned int :0;
unsigned int hour:5;
};
大小为8,可以定义长度为0的位域成员,其作用是迫使下一个成员从下一个完整的机器字开始分配空间。
在设计位域的时候,最好不要让一个位域成员跨越一个不完整的字节来存放,因为这样会增加计算机运算的开销。另外,不能取一个位域对象的数据成员的地址。使用位域节省存储空间会导致程序运行速度的下降,因为计算机无法直接寻址到单个字节的某些位,必须通过额外的代码来实现。这种矛盾是由计算机的组成原理决定的,在内存空间和运行速度无法同时优化的情况下,由应用需求决定优化哪一个。
2、成员对齐
既不损失cpu对对象的访问效率,又要尽可能的压缩对象的存储空间,这一直视我们追求的对象设计目标之一。对于复合类型的数据,如果它的起始地址能够满足其中要求最为严格的那个数据成员的自然对齐要求,那么它就是自然对齐的;如果那个数据成员又是一个复合类型的对象,则以此类推,直到最后都是基本数据类型的数据成员。至于先声明的成员被放在高地址还是低地址,完全是由编译器实现的,一般都会采用“按照声明的顺序从低地址到高地址一次存放每个成员”的方案。同时,为了满足各个成员的对齐要求,各个成员之间甚至对象的末尾可能会插入一定量的填充字节。
struct X { char m_ch; char *m_pStr; }; //8,按4字节对齐 struct Y { char m_ch; int m_count; }; //8,按4字节对齐 struct X { bool m_ok; char m_name[6]; }; //7,按1字节对齐 struct R { char m_ch; double m_width; char m_name[6]; }; //24,按8字节对齐 struct T { int m_no; R m_r; }; //32,按8字节对齐 struct U { bool m_ok; T m_t; }; //40,按8字节对齐
当然可以通过pragma来改变对齐方式
#include<iostream> using namespace std; #pragma pack(push,4) struct R { char m_ch; double m_width; char m_name[3]; }; #pragma pack(pop) int main(void) { R r; cout << &r << endl; cout << (void *)&r.m_ch << endl; //将char *转换为void *,不然输出字符串啊 cout << &r.m_width << endl; cout << &r.m_name << endl; cout << sizeof(r) << endl; //16,按4字节对齐 return 0; }
3、字节对齐可能带来的隐患
代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678; unsigned char *p=NULL; unsigned short *p1=NULL; p=&i; *p=0x00; p1=(unsigned short *)(p+1); *p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.