By zieckey (http://blog.chinaunix.net/u/16292/)
本文测试环境是Linux系统,FC7,gcc 版本 4.1.2 20070502
表示方式:
内存地址 (H)字节内容(L)
0x0000 0000 B3B2 B1B0
0x0000 0004 B3B2 B1B0
0x0000 0008 B3B2 B1B0
...
...
...
首先看下面的程序:
#include
char g_c1;
short g_s;
char g_c2;
int g_i;
int main(void)
{
char c1;
short s;
char c2;
int i;
printf("sizeof: char=%d,short=%d,int=%d\n", sizeof(char), sizeof(short), sizeof(int));
printf( "Global variable init value is 0: g_c1=%d, g_s=%d, g_c2=%d, g_i=%d\n", g_c1, g_s, g_c2, g_i);
printf("Local variable init value is random: c1=%d, s=%d, c2=%d, g_i=%d\n", c1, s, c2, i);
printf("Global variable: g_c1=%p, g_s=%p, g_c2=%p, g_i=%p\n", &g_c1, &g_s, &g_c2, &g_i);
printf("Local variable: c1=%p, s=%p, c2=%p, i=%p\n", &c1, &s, &c2, &i);
return 0;
}
运行输出:
sizeof: char=1,short=2,int=4
Global variable init value is 0: g_c1=0, g_s=0, g_c2=0, g_i=0
Local variable init value is random: c1=80, s=12276, c2=79, g_i=1096046812
Global variable: g_c1=0x8049944, g_s=0x8049946, g_c2=0x8049948, g_i=0x804994c
Local variable: c1=0xbfe9562f, s=0xbfe9562c, c2=0xbfe9562b, i=0xbfe95624
全局变量由编译器自动初始化为0,局部变量初始化的值是随机的。
下面看变量的内存分配:
全局变量(放在静态数据存储区,全局可见):
内存地址 (H)字节内容(L)
g_s 和 g_c1 BBxB 由低位字节开始 , g_c1 放置在B0字节, g_s 放置在B3B2两个字节处(g_c1=0x8049944)
0x0804 9944 B3B2 B1B0
g_c2 xxxB 由低位字节开始 , g_c2 放置在B0字节(g_c2=0x8049948)
0x0804 9948 B3B2 B1B0
i BBBB g_i 放置在 B3B2B1B0 四个字节,一个机器字长
0x0804 994c B3B2 B1B0
内存存放时候有字节序对齐,以32位为准,且在内存中存放的顺序与定义时的顺序一致,即先定义的变量在内存中的低地址,后定义的变量在高地址。
同时,不足四字节的变量,优先放在一个机器字长(4字节)低位。
局部变量(放在栈空间,局部可见):
内存地址 (H)字节内容(L)
i BBBB i 放置在 B3B2B1B0 四个字节,一个机器字长
0xbfe9 5624 B3B2 B1B0
c2 Bxxx 由高位字节开始 , c2 放置在B3字节(c2=0xbfe9562b)
0xbfe9 5628 B3B2 B1B0
c1 和 s BxBB 由高位字节开始 , c1 放置在 B3 高位字节,s放在 B1B0 低位字节
0xbfe9 562C B3B2 B1B0
内存存放时候有字节序对齐(内存对齐),以32位为准,在内存中存放的顺序与定义时的顺序正好相反,即先定义的变量在内存中的高地址,后定义的变量在低地址。
同时,不足四字节的变量,优先放在一个机器字长(4字节)高位。
为什么会有内存对齐
以下内容节选自《Intel Architecture 32 Manual》。
字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被 16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。
下面再看看结构体的内存空间:
#include
struct foo
{
char c1;
short s;
char c2;
int i;
};
struct bar
{
char c1;
char c2;
short s;
int i;
};
#pragma pack(1)
struct foo_pack
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()
int main(void)
{
struct foo a;
struct bar b;
struct foo_pack p;
printf("struct foo c1=%p, s=%p, c2=%p, i=%p\n", &a.c1,&a.s,&a.c2,&a.i);
printf("struct bar c1=%p, s=%p, c2=%p, i=%p\n", &b.c1,&b.s,&b.c2,&b.i);
printf("struct foo_pack c1=%p, s=%p, c2=%p, i=%p\n", &p.c1,&p.s,&p.c2,&p.i);
printf("sizeof foo is %d\n", sizeof(struct foo));
printf("sizeof bar is %d\n", sizeof(struct bar));
printf("sizeof foo_pack is %d\n", sizeof(struct foo_pack));
return 0;
}
程序输出:
struct foo c1=0xbfe40dd8, s=0xbfe40dda, c2=0xbfe40ddc, i=0xbfe40de0
struct bar c1=0xbfe40dd0, s=0xbfe40dd2, c2=0xbfe40dd1, i=0xbfe40dd4
struct foo_pack c1=0xbfe40dc8, s=0xbfe40dc9, c2=0xbfe40dcb, i=0xbfe40dcc
sizeof foo is 12
sizeof bar is 8
sizeof foo_pack is 8
缺省情况下,c/c++编译器默认将结构、栈中的成员数据进行内存对齐。因此,
struct foo
{
char c1;
short s;
char c2;
int i;
};
printf("struct foo c1=%p, s=%p, c2=%p, i=%p\n", &a.c1,&a.s,&a.c2,&a.i);
的输出为:
struct foo c1=0xbfe675f8, s=0xbfe675fa, c2=0xbfe675fc, i=0xbfe67600
c1、s各占两字节,c2和i各占4字节
编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有空洞),但提高了性能。
也正是这个原因,我们不可以断言sizeof(foo) == 8。在这个例子中,sizeof(foo) == 12。
如何避免内存对齐的影响
那么,能不能既达到提高性能的目的,又能节约一点空间呢?有一点小技巧可以使用。比如我们可以将上面的结构改成:
struct bar
{
char c1;
char c2;
short s;
int i;
};
这样一来,每个成员都对齐在其自然边界上,从而避免了编译器自动对齐。在这个例子中,sizeof(bar) == 8。
如何使用c/c++中的对齐选项
有3种办法:
1. gcc的编译选项“-fpack-struct”
可以去除struct结构中额外的hole,缺点是,影响应用中所有的struct,包括从其它库中引入的struct结构
2. __attribute__ ((packed)) 声明
struct {char a; double b;} xyz __attribute__ ((packed));
不过这种简单格式的声明只对C有效,在C++中,你必须对struct中的每个成员(size>1)进行这样的声明:
struct {
char a;
double b __attribute__ ((packed));
int c __attribute__ ((packed));
} xyz;
3. 预编译选项 #pragma pack(n)
这里的n是通知编译器对此“pragma”行后出现的所有数据结构(包括stuct/union)采用n字节方式对齐(align)。如果n=1,那么表示全紧凑,struct中不会出现任何占位的hole。如果n是空,表示回复到编译器缺省的设置(一般=8)
#pragma pack(1)
//此段内所有数据结构全紧凑
#pragma pack()
比如:
#pragma pack(1)
struct foo_pack
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()
这样sizeof(struct foo_pack)=8
参考:
unix环境高级编程第二版p152
阅读(767) | 评论(0) | 转发(0) |