Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4242017
  • 博文数量: 176
  • 博客积分: 10059
  • 博客等级: 上将
  • 技术积分: 4681
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-24 12:27
文章分类

全部博文(176)

文章存档

2012年(1)

2011年(4)

2010年(14)

2009年(71)

2008年(103)

分类: C/C++

2008-05-03 19:39:22

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


阅读(2521) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~