Chinaunix首页 | 论坛 | 博客
  • 博客访问: 155891
  • 博文数量: 38
  • 博客积分: 1474
  • 博客等级: 上尉
  • 技术积分: 580
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-11 17:01
文章分类

全部博文(38)

文章存档

2008年(38)

分类: C/C++

2008-04-21 15:52:41

为了能使CPU对变量进行高效快速的访问,变量的起始地址应该具有某些特性,
即所谓的“对齐”。例如对于4字节的int类型变量,其起始地址应位于4字节边界上,
即起始地址能够被4整除。变量的对齐规则如下(32位系统):


Type
Alignment

char
在字节边界上对齐

short (16-bit)
在双字节边界上对齐

int and long (32-bit)
在4字节边界上对齐

float
在4字节边界上对齐

double
在8字节边界上对齐

 

字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
 
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成

  员之间加上填充字节;例如上面第二个结构体变量的地址空间。

3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员

   之后加上填充字节。
例如:


struct s1
{
 char a;
 short b;
 char c;
};

  在上述结构体中,size最大的是short,其长度为2字节,因而结构体中的char成员a、c都以2

为单位对齐,sizeof(s1)的结果等于6;
      
                   a               b              c
s1的内存布局        1*              11              1* 

(*表示填充的字节,下同)
若改为
struct s1
{
 char a;
 int b;
 char c;
};

  其结果显然为12。
                   a               b                c
s1的内存布局        1***           1111           1***
 
2.又如:
       struct S1{
                  char a;
                  long b;
               };
       struct S2 {
                  char c;
                  struct S1 d;
                  long long e;
                 };
sizeof(S2)结果为24;

分析如下:
   根据上面的分析sizeof(s1)=8;
        

    s1的内存布局                   

            a           b                                             1***         1111 

    S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?

于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,

成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节

的边界上,这时, 已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员

e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样, 一共使用了24个字节.
  

S2的内存布局        

c                d                          e
1***       1***1111****         11111111

再看两个结构体成员比较复杂的例子:
  Typedef struct student
{

       Char name[10];
       Long sno;
       Char sex;
       Float score [4]; 

} STU;
sizeof(STU)=?
STU的内存布局           

   name            sno       sex         score
1111111111**      1111       1***       1111111111111111
sizeof(STU)=10+2+4+1+3+16
如果我们把STU中的成员改一下顺序:
 Typedef struct student
{
       Char name[10];
       Char sex;
       Long sno;
       Float score [4];
} STU;

STU的内存布局       

   name         sex       sno        score
1111111111      1*       1111       1111111111111111

sizeof(STU)=10+2+4+16
注意:

1) 对于空结构体,sizeof == 1;因为必须保证结构体的每一个实例在内存中都
有独一无二的地址。

2) 结构体的静态成员不对结构体的大小产生影响,因为静态变量的存储位置与
结构体的实例地址无关。

例如:

struct {static int I;} T;
sizeof(T) == 1;
struct {char a; static int I;} T1;
sizeof(T1) == 1;

----------------------------------------------------------------------


struct的成员对齐:

  Intel、微软等公司曾经出过一道类似的面试题:

1. #include

2. #pragma pack(8)
3. struct example1
4. {
5. short a;
6. long b;
7. };

8. struct example2
9. {
10. char c;
11. example1 struct1;
12. short e;    
13. };
14. #pragma pack()

15. int main(int argc, char* argv[])
16. {
17. example2 struct2;

18. cout << sizeof(example1) << endl;
19. cout << sizeof(example2) << endl;
20. cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2)
<< endl;

21. return 0;
22. }
  问程序的输入结果是什么?

  答案是:

8
16
4

  不明白?还是不明白?下面一一道来:

2.1 自然对界

   struct是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如 array、struct、union等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。缺省情况下,编译器为结构体的每个 成员按其自然对界(natural alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

  自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中size最大的成员对齐。

  例如:

struct naturalalign
{
char a;
short b;
char c;
};
  在上述结构体中,size最大的是short,其长度为2字节,因而结构体中的char成员a、c都以2为单位对齐,sizeof(naturalalign)的结果等于6;

  如果改为:

struct naturalalign
{
char a;
int b;
char c;
};
  其结果显然为12。

2.2指定对界

  一般地,可以通过下面的方法来改变缺省的对界条件:

  · 使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
  · 使用伪指令#pragma pack (),取消自定义字节对齐方式。

  注意:如果#pragma pack (n)中指定的n大于结构体中最大成员的size,则其不起作用,结构体仍然按照size最大的成员进行对界。

  例如:

#pragma pack (n)
struct naturalalign
{
char a;
int b;
char c;
};
#pragma pack ()
  当n为4、8、16时,其对齐方式均一样,sizeof(naturalalign)的结果都等于12。而当n为2时,其发挥了作用,使得sizeof(naturalalign)的结果为8。

  在VC++ 6.0编译器中,我们可以指定其对界方式,其操作方式为依次选择projetct > setting > C/C++菜单,在struct member alignment中指定你要的对界方式。

  另外,通过__attribute((aligned (n)))也可以让所作用的结构体成员对齐在n字节边界上,但是它较少被使用,因而不作详细讲解。

2.3 面试题的解答

  至此,我们可以对Intel、微软的面试题进行全面的解答。

   程序中第2行#pragma pack (8)虽然指定了对界为8,但是由于struct example1中的成员最大size为4(long变量size为4),故struct example1仍然按4字节对界,struct example1的size为8,即第18行的输出结果;

  struct example2中包含了struct example1,其本身包含的简单数据成员的最大size为2(short变量e),但是因为其包含了struct example1,而struct example1中的最大成员size为4,struct example2也应以4对界,#pragma pack (8)中指定的对界对struct example2也不起作用,故19行的输出结果为16;

  由于struct example2中的成员以4为单位对界,故其char变量c后应补充3个空,其后才是成员struct1的内存空间,20行的输出结果为4。

---------------------------------------------------------

探讨:内存对齐

Tue Dec 19 21:05:23 2006

 
朋友帖了如下一段代码:
  #pragma pack(4)
  class TestB
  {
  public:
    int aa;
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestB);
  这里nSize结果为12,在预料之中。

  现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestC);
  按照正常的填充方式nSize的结果应该是8,为什么结果显示nSize为6呢?

事实上,很多人对#pragma pack的理解是错误的。
#pragma pack规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,
按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不
产生任何效果。
而结构整体的对齐,则按照结构体中最大的数据成员 和 #pragma pack指定值 之间,较小
的那个进行。

具体解释
#pragma pack(4)
  class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以
这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以
放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
这个类实际占据的内存空间是9字节
类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一
个对齐的。
所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。
9按照4字节圆整的结果是12,所以sizeof(TestB)是12。


如果
#pragma pack(2)
    class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以
这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以
放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
//可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是
10。
//所以 sizeof(TestB)是10。

最后看原贴:
现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;//第一个成员,放在[0]偏移的位置,
    short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以
放在偏移[2,3]的位置。
    char c;//第三个,自身长为1,放在[4]的位置。
  };
//整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果
是6
//所以sizeof(TestC)是6。

感谢 Michael 提出疑问,在此补充:

当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里
指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然
后取其中较大的。

可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐
的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实
际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照
#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:

在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇
到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长
度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的
数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( al
ign() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,
取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作
用。



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