关于sizeof和内存对齐

感觉这篇文章分析的还算比较到位,故转之。
转自: http://www.52rd.com/Blog/Archive_Thread.asp?SID=10488

本来,一般是不自己计算sizeof的,知道内存对齐会对sizeof有影响,所以从来不手算,而是代码里写上sizeof。今天又看到http://blog.vckbase.com/smileonce/archive/2005/08/08/10658.html,翻来了http://blog.vckbase.com/billdavid/archive/2004/06/23/509.html ,自己想想还是也记录一下,万一以后自己真的也要计算sizeof,忘了,还能有个提示,也给不是很明白的朋友一个参考。
struct sample1
{
    char a;        /// sizeof(char) = 1
    double b;    /// sizeof(double) = 8
};
///default(  缺省#pragam pack(8) ——VC6和VC71,其它编译器,个人未知 )
    ///1+8 = 9 —> 16(  8 < 9 < 16  )

#pragma pack( 4 )
    ///1+8 = 9 —> 12(  8 < 9 < 12  )

#pragma pack( 2 )
    ///1+8 = 9 —> 10(  8 < 9 < 10  )

#pragma pack( 1 )
    ///1+8 = 9 —> 9

#pragma pack( 16 )
    ///1+8 = 9 —> 16(  16—>8 —- 8 < 9 < 16  )

struct sample2
{
    char a;     ///1
    int b;        ///4
};
#pragma pack( 8 )
    /// 1 + 4  = 5 —> 8(  8 —> 4  )

#pragma pack( 16 )
    /// 1 + 4 = 5 —> 8( 16 —> 4  )

    说明:#pragma pack告诉编译器进行内存边界对齐,一般都是采用编译器的设置对整个项目采用同一对齐方案,而且通常为缺省8字节对齐。

/////////////////////////////////以下内容于 2005-12-10 添加/////////////////////////////////

    今天又看到以前测试的一段代码,突然不明白了起来,又稍写了几个测试。
struct sample3
{
    char a;  ///1
    int b;    ///4
    char c;  ///1
};
///default                  ///12
#pragma pack( 4 )   ///12
#pragma pack( 2 )   ///08
#pragma pack( 1 )   ///06
#pragma pack( 16 ) ///12

    原来,其实编译器,根据对齐指示的对齐字节和最大成员的字节,对每个成员进行了对齐:编译器会取对齐指示和最大成员字节中较小的一个用于补齐其它成员。那么,上面的sample1/2/3也就都在情理之中了。为了证实这点,我们还再看一个例子:
struct sample4
{
    char a;      ///1
    int b;         ///4
    double c;  ///8
    char d;      ///1
};
///default:                ///8+8+8+8 = 32
#pragma pack( 4 )   ///4+4+8+4 = 20
#pragma pack( 2 )   ///2+4+8+2 = 16
#pragma pack( 1 )   ///1+4+8+1 = 14
#pragma pack( 16 ) ///8+8+8+8 = 32
而实际上,编译器给出的值是:24、20、16、14、24
那么说明我错了。注意一下,我发现char a,int b加起来也才5<8,难到编译器进行了联合对齐?
struct sample5
{
    char a;      ///1
    double c;  ///8
    int b;         ///4
    char d;      ///1
};
编译器给出结果:24、20、16、14、24

    这用联合对齐的解释正好符合,我又试验了不同的数据,发现这个结论并不太准确确。于是,我输出了每一个对象成员地址进行分析。由于试验数据量很大,这里就不列出了。

最后得到了以下结论:
    1. 成员的对齐是按声明顺序进行的;
    2. 对齐值由编译指示和最大成员两者较小的值决定;
    3. 未对齐到对齐值的成员一起形成块对齐(联合对齐);
    4. 上一个(下一个)对齐采用自己较大则不变,自己较小则填充自己对齐到上一个(下一个)大小;
    5. 每成员对齐:如果前面已对齐到对齐值,下一个对齐自己。如果前面未对齐到对齐值,如果加上下一个成员不大于对齐值,下一个对齐自己,否则填充自己块对齐到对齐值。
    6. 最后还未对齐到对齐值的,填充空间块对齐到对齐值。

从这些结论,可以得到:
    1. 以上的对齐原则其实是尽量整齐排列、尽量节省内存。
    2. 声明成员应该尽量避免不同类型错杂开来,最好采用从小到大或者从大到小的顺序(错开后,会因为上对齐和下对齐而增加填充开销)。
    3. 编译器缺省采用8字节对齐主要是因为最大基本类型为8自己(以前自己不明白,在论坛提过问,后来,以为是SSE指令的原因)。
    4. 手算sizeof是没有必要的,负责的(可以先对齐出对齐块,用块数乘对齐值)。

 
载自 :http://blog.vckbase.com/zhangjw_cn/archive/2005/08/09/10701.html
 
 
—————————————————————————————————-
 
文章二:

#pragma pack和数据对齐问题

结构数据存放时默认按4对齐,考虑以下程序,输出结果为:sizeof(A)=12

typedef struct _A
{
    
int x;
    
char z[7];
}
A;

void main()
{
    
int len = sizeof(A);
    printf(
"sizeof(A)=%d\n", len);    // len = 12
}

使用 #pragma pack,设置数据按1对齐,此时输出结果为:sizeof(A)=11

#pragma pack(push)
#pragma pack(
1)
typedef 
struct _A
{
    
int x;
    
char z[7];
}
A;
#pragma pack(pop)

void main()
{
    
int len = sizeof(A);
    printf(
"sizeof(A)=%d\n", len);    // len = 11
}
 
—————————————————————————————————-

文章三:

解惑:sizeof(联合)这个值是怎么计算的

[不要只做技术]在论坛上问如下代码结果为什么是24?

这个问题很好回答,并且我把这个问题归结于基本概念题(就是入门书必须介绍的)。我想一般来说,做过内存管理的,对这个语言特性肯定不会陌生。

摘几句The C Programming Language里面讲述这个问题的原话,以说明读书还是必要的:
①联合就是一个结构,②它的所有成员相对于基地址的偏移量都为0,③此结构空间要大到足够容纳最“宽”的成员,④并且,其对齐方式要适合于联合中所有类型的成员。

怕有的兄弟还不明白,特附图一个帮助理解:

   

    

        

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            

        

        

            

            

            

            

            

            

            

            

        

        

            

            

            

            

            

        

    

char a; => x                                              
int i[5]; =>             

 x

            

            

 x

            

            

 x

            

            

 x

            

            

 x

            

            

x

            

double b; =>             

 x

            

            

 

            

            

 

            

该结构要放得下int  i[5]必须要至少占4×5=20个字节。如果没有double的话20个字节够用了,此时按4字节对齐。但是加入了double就必须考虑double 的对齐方式,double是按照8字节对齐的,所以必须添加4个字节使其满足8×3=24,也就是必须也是8的倍数,这样一来就出来了24这个数字。综上 所述,最终联合体的最小的size也要是所包含的所有类型的基本长度的最小公倍数才行。(这里的字节数均指winnt下的值,平台、编译器不同值也有可能 不同。)

联合在存储分配的时候用的机会最多,因为很少有像存储分配这样需要给多种不同类型的变量分配空间而又打算尽可能的节约内存的,这很适合联合的特性。上述对齐的方式有个很有趣的用法也就常在存储分配里面使用。(下面依旧用The C Programming Language中的例子作答)

这里的Align有什么用?作用只有一个,就是强迫分配的结构体按long的长度对齐。

========== test add by lijunjie =========
struct nn {
    float f;  // 4
    char c1;  // 1 -> 4
    long l;   // 4
    int i;    //4
    char c2;  // 1 -> 4
};

不管是 #pragma pack(4) 还是 #pragma pack(8) ; 因为成员的最大宽度为4,所以最大按照4字节对齐,;因为 c1 的前后成员都能自己对齐,所以c1只好自己完成对齐, 故sizeof 的结果为20;

或许该结构体写成如下方式更合理一些:
struct nn {
    float f;  // 4
    long l;   // 4
    int i;    //4
    char c1;  // c1 和 c2 联合后对齐, 故 1 + 1 = 2 -> 4
    char c2; 
};
故sizeof的结果为 16

=========== 相关文章 ===========
http://westsoftware.blog.163.com/blog/static/26094109201021301421829/

留下评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据