在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列,而不是简单地顺序排列,这就是内存对齐。
内存对齐的原因:
1)某些平台只能在特定的地址处访问特定类型的数据;
2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
win32平台下的微软C编译器对齐策略:
1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。
2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要求。
3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。
一、结构体中只有简单数据类型,例如:
typedef struct s{
int a;
short b;
char c;
}s1;
分析如下:(一个字符代表一个字节)
*********************
aaaa
bbc-
*********************
s1的大小是8。
typedef struct s{
char a;
int b;
short c;
}s1;
分析如下:(一个字符代表一个字节)
*********************
a---
bbbb
cc--
*********************
s1的大小是12。
二、结构体中含有结构体,对齐长度以包含子结构体在内的所有的基本类型为准,下例即以double为准例如:
typedef struct s{
long a;
int b[5];
double c;
}s1;
typedef struct ss{
int d;
s1 e;
long f;
}s2;
分析如下:(一个字符代表一个字节)
*********************
dddd----
aaaabbbb
bbbbbbbb
bbbbbbbb
cccccccc
ffffffff
*********************
s2的大小为48,由于第二条规则的限制所以子结构体中的a的首地址必须是本结构体中成员最大宽度的整数倍所以a从第二行开始,而不是第一行开始,如果最大宽度不在子结构体中则会不同,看下一个例子。
typedef struct s{
long a;
int b[5];
long c;
}s1;
typedef struct ss{
int d;
s1 e;
double f;
}s2;
分析如下:(一个字符代表一个字节)
*********************
ddddaaaa
bbbbbbbb
bbbbbbbb
bbbbcccc
ffffffff
*********************
s1的大小是40。
三、结构体中含有共用体,对齐宽度以所有基本类型最大者为准
typedef union s{
long a;
int b[5];
long c;
}s1;
typedef struct ss{
int d;
s1 e;
double f;
}s2;
分析如下:(一个字符代表一个字节)
*********************
ddddbbbb
bbbbbbbb
bbbbbbbb
ffffffff
*********************
s1的大小是32。如果将共用体s1的c改为double,结构体中s2的f改为long,则大小就会变为40也是因为第二条规则的原因。
四、结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的),所以静态数据成员既不影响该结构体的大小,也不影响该结构体的对齐。计算大小时可以直接忽略该静态变量。
内存对齐的原因:
1)某些平台只能在特定的地址处访问特定类型的数据;
2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
win32平台下的微软C编译器对齐策略:
1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。
2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要求。
3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。
一、结构体中只有简单数据类型,例如:
typedef struct s{
int a;
short b;
char c;
}s1;
分析如下:(一个字符代表一个字节)
*********************
aaaa
bbc-
*********************
s1的大小是8。
typedef struct s{
char a;
int b;
short c;
}s1;
分析如下:(一个字符代表一个字节)
*********************
a---
bbbb
cc--
*********************
s1的大小是12。
二、结构体中含有结构体,对齐长度以包含子结构体在内的所有的基本类型为准,下例即以double为准例如:
typedef struct s{
long a;
int b[5];
double c;
}s1;
typedef struct ss{
int d;
s1 e;
long f;
}s2;
分析如下:(一个字符代表一个字节)
*********************
dddd----
aaaabbbb
bbbbbbbb
bbbbbbbb
cccccccc
ffffffff
*********************
s2的大小为48,由于第二条规则的限制所以子结构体中的a的首地址必须是本结构体中成员最大宽度的整数倍所以a从第二行开始,而不是第一行开始,如果最大宽度不在子结构体中则会不同,看下一个例子。
typedef struct s{
long a;
int b[5];
long c;
}s1;
typedef struct ss{
int d;
s1 e;
double f;
}s2;
分析如下:(一个字符代表一个字节)
*********************
ddddaaaa
bbbbbbbb
bbbbbbbb
bbbbcccc
ffffffff
*********************
s1的大小是40。
三、结构体中含有共用体,对齐宽度以所有基本类型最大者为准
typedef union s{
long a;
int b[5];
long c;
}s1;
typedef struct ss{
int d;
s1 e;
double f;
}s2;
分析如下:(一个字符代表一个字节)
*********************
ddddbbbb
bbbbbbbb
bbbbbbbb
ffffffff
*********************
s1的大小是32。如果将共用体s1的c改为double,结构体中s2的f改为long,则大小就会变为40也是因为第二条规则的原因。
四、结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的),所以静态数据成员既不影响该结构体的大小,也不影响该结构体的对齐。计算大小时可以直接忽略该静态变量。
五、程序中可以用#pragma pack(n)命令强制以n字节对齐时,n取值为1,2,4,8,16默认情况下n为8.结构体中成员的sizeof值和n比较,取小者作为偏移地址的参考值。比如n=2,结构体有个一个int成员,当计算int空间是偏移地址应该是n的整数倍,而不是sizeof(int)的整数倍。如果有结构体嵌套,那么注意子结构体的第一个成员地址必须是能被(n和sizeof(最大宽度成员)中最小值)整除,即第一条规则。