原文出处:http://www.openedv.com/posts/list/0/13034.htm#349497
在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何 变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐。
内存对齐的原因:
1)某些平台只能在特定的地址处访问特定类型的数据;
2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
win32平台下的微软C编译器对齐策略:
1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。
2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空 间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要 求。
3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。
下面看一下sizeof在计算结构体大小的时候具体是怎样计算的
1.test1 空结构体
typedefstructnode
{
}S;
|
则sizeof(S)=1;或sizeof(S)=0;
在C++中占1字节,而在C中占0字节。
2.test2
typedefstructnode1
{
inta;
charb;
shortc;
}S1;
|
则sizeof(S1)=8。这是因为结构体node1中最长的数据类型是int,占4个字节,因此以4字节对齐,则该结构体在内存中存放方式为
|--------int--------| 4字节
|char|----|--short-| 4字节
总共占8字节
3.test3
typedefstructnode2
{
chara;
intb;
shortc;
}S2;
|
则siezof(S3)=12.最长数据类型为int,占4个字节。因此以4字节对齐,其在内存空间存放方式如下:
|char|----|----|----| 4字节
|--------int--------| 4字节
|--short--|----|----| 4字节
总共占12个字节
4.test4 含有静态数据成员
typedefstructnode3
{
inta;
shortb;
staticintc;
}S3;
|
则sizeof(S3)=8.这里结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。其在内存中存储方式如下:
|--------int--------| 4字节
|--short-|----|----| 4字节
而变量c是单独存放在静态数据区的,因此用siezof计算其大小时没有将c所占的空间计算进来。
5.test5 结构体中含有结构体
typedefstructnode4
{
boola;
S1 s1;
shortb;
}S4;
|
则sizeof(S4)=16。是因为s1占8字节,而s1中最长数据类型为int,占4个字节,bool类型1个字节,short占2字节,因此以4字节对齐,则存储方式为
|-------bool--------| 4字节
|-------s1----------| 8字节
|-------short-------| 4字节
6.test6
typedefstructnode5
{
boola;
S1 s1;
doubleb;
intc;
}S5;
|
则sizeof(S5)=32。是因为s1占8字节,而s1中最长数据类型为int,占4字节,而double占8字节,因此以8字节对齐,则存放方式为:
|--------bool--------| 8字节
|---------s1---------| 8字节
|--------double------| 8字节
|----int----|---------| 8字节
7.test7
若在程序中使用了#pragma pack(n)命令强制以n字节对齐时,默认情况下n为8.
则比较n和结构体中最长数据类型所占的字节大小,取两者中小的一个作为对齐标准。
若需取消强制对齐方式,则可用命令#pragma pack()
如果在程序开头使用命令#pragma pack(4),对于下面的结构体
typedefstructnode5
{
boola;
S1 s1;
doubleb;
intc;
}S5;
|
则sizeof(S5)=24.因为强制以4字节对齐,而S5中最长数据类型为double,占8字节,因此以4字节对齐。在内存中存放方式为:
|-----------a--------| 4字节
|--------s1----------| 4字节
|--------s1----------| 4字节
|--------b-----------| 4字节
|--------b-----------| 4字节
|---------c----------| 4字节
总结一下,在计算sizeof时主要注意一下几点:
1)若为空结构体,则只占1个字节的单元
2)若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数
若结构体中数据类型不同,则取最长数据类型成员所占的空间为对齐标准,数据成员包含另一个结构体变量t的话,则取t中最 长数据类型与其他数据成员比较,取最长的作为对齐标准,但是t存放时看做一个单位存放,只需看其他成员即可。
3)若使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。
另外除了结构体中存在对齐之外,普通的变量存储也存在字节对齐的情况,即自身对齐。编译器规定:普通变量的存储首地址必须能被该变量的数据类型宽度整除。
测试程序:
1 | typedefstructnode |
2 | { |
3 | }S; |
1 | typedefstructnode5 |
2 | { |
3 | boola; |
4 | S1 s1; |
5 | doubleb; |
6 | intc; |
7 | }S5; |
联合(Union)
是一种构造数据类型,它提供了一种使不同类型数据类型成员之间共享存储空间的方法,同时可以实现不同类型数据成员之间的自动类型转换。联合体对象在同一时间只能存储一个成员的值。
联合的内存大小取决于其中字节数最多的成员,而不是累加,联合也会进行字长对齐。在定义联合变量的时候可以指定初始值,但是只能制定一个初始值(测试过似乎无法设置初值),而且该初始值的类型必须与联合的第一个成员的类型匹配。可以取一个联合变量的地址,也可以取变量中的任一个成员的地址,它们总是相等的。可以在同类型的联合变量之间赋值,但是不能比较两个联合变量的大小,不只是因为可能存在填补字节的问题,而且这两个变量可能是不同类型的成员,因此代表了两个类型不同的变量。
有这么几种定义联合体的方法:
1)标准方法
{
int b;
char c;
};
a abc;
abc.b=4;
abc.c=5;
//或者也可以直接在定义时定义变量
union a
{
int b;
char c;
}abc;
abc.b=4;
abc.c=5;
2)使用typedef。这样相当于重新定义一个自定义类型,a作为类型名称。而不是如上面的变量名称。
{
int b;
char c;
}a;
a abc;
abc.b=4;
abc.c=5;
3)不定义类型而只定义实例名称。这种方法用来局部使用union,而已在指定的某个作用域下使用。此时abc只是变量名称,而不是类型名称。
{
int b;
char c;
}abc;
abc.b=5;
abc.c=6;
4)匿名联合体(anonymous union),这种联合体比较特殊,它既不定义类型名称,也不定义变量名称。这种联合体只能作用在struct内部,相当于结构的一个成员。
{
int b;
union
{
int c;
char d;
};
};
a abc;
abc.b=5;
abc.c=6;
abc.d=7;
这里有一种利用联合体来判断平台属性的简单程序,可以快速地判断平台是大端还是小端模式。如果函数返回0则表示平台是大端模式,否则是小端模式。
2{
3 union w
4 {
5 int a;
6 char b;
7 }c;
8 c.a = 1;
9 return(c.b==1);
10}
struct {
int a;
double b;
};
struct {
char* c;
unsigned d;
};
};
枚举(Enum)
定义的一组特殊用途的符号常量,它表示这种类型的变量可以取值的范围。
定义枚举类型的时候,如果不特别指定其中的标示符的值,则第一个标示符的值将是0,后面的比前面的依次大1。如果指定了某一个标示符的值,后面的再前面的基础上依次大1。如
enum Week{Sun, Mon=125, Tue, Wed, Thu=140, Fri, Sat};
其中的符号常量的值依次为:0, 125, 126, 127, 140, 141, 142。
也可以把某些枚举常量初始化为相等的常量,如
enum ABC{A=1, B=1, C=100};
另外,枚举类型还可以是匿名的,即匿名枚举(anonymous enum)。匿名的枚举类型就相当于直接定义的const符号常量,可以作为全局枚举,也可以放在任何类定义或名字空间中。
enum
{
OBJECT_CREATIONG =0x10;
OBJECT_DELETIONG =0x11;
STATE_CHANGE =0x12;
ATTR_VALUE_CHANGE =0x13;
};
它可以取代宏常量和const符号常量。(以后看见这样的枚举类型就不会再觉得奇怪了)