CSAPP读书笔记——结构体的内存对齐

分配内存时,许多计算机系统会对内存的地址施加一定的规则。一般来讲,分配的内存的地址最好是K的倍数(K = 2,4,8)。这个设定的目的是简化处理器与内存系统的接口设计,这样分配内存的操作只需要用一条指令即可完成
不同的计算机系统有不同的限制规则,道理来讲,这种K字节强制对齐虽然会浪费一些内存,但是会提高效率。
为此,微软的操作系统强制要求:

KK

我们举几个例子。

struct S1{
    int  item1;
    char item2;
    int  itme3;
};

假设定义如上结构体,我们理想中的内存分布应该是这样的。

CSAPP读书笔记——结构体的内存对齐_第1张图片

不过这样就不满足内存对齐的原则(item3是int,它的起始地址必须为4的倍数)。

实际上,在内存中形式如下:

没错,有3个字节的内存空间被“浪费”了,不过为了简化指令系统,这点牺牲是值得的。

考虑:

struct S2{
    int  item1;
    int  itme2;
    char item3;
};

我们对结构体的各个域的顺序做了一下调整,使得item3也“满足”了内存对齐的要求:

但是,考虑下列声明式子:

struct S2 d[4];

如果还是上述的内存模型,d[0]的确满足,可是d[1]就不满足的了(d[1].item1的起始地址为10不是4的倍数),为了避免这种情况的发生,对于S2,仍然采用以下对齐方式:

CSAPP读书笔记——结构体的内存对齐_第2张图片

这个例子我们看不出顺序对于整个结构体大小的影响,但是实际上遵循某种规则我们可以精简结构体所占的空间,尽量少的浪费内存。

我们看下面一个例子:

typedef struct{
    char       *a;
    short      b;
    double     c;
    char       d;
    float      e;
    char       f;
    long long  g;
    void      *h;
}S4;

它的内存模型大致如下 :

CSAPP读书笔记——结构体的内存对齐_第3张图片

我们稍微分析一下 :

a起始地址为0 ,满足内存对齐原则,最少占用4个字节。
b起始地址为4 ,满足内存对齐原则,最少占用4个字节。
c起始地址为8 ,满足内存对齐原则, 最少占用8个字节。
d起始地址为16,满足内存对齐原则, 最少占用1个字节,但是下一个域e的起始地址必须为sizeof(float) = 4的倍数,因此d占用了4个字节的内存,有3个字节的内存被“浪费”了。
e起始地址为20 ,满足内存对齐原则,最少占用4个字节。
f起始地址为24 ,满足内存对齐原则,最少占用1个字节,但是下一个域g的起始地址必须为sizeof(long long) = 8的倍数,因此f占用8个字节的内存,有7个字节的内存被“浪费”了。
g起始地址为32,满足内存对齐原则,最少占用8个字节。
h起始地址为40,满足内存对齐原则,最少占用4个字节,但是为了满足8字节对齐原则(因为整个结构体中单个域占用的最大内存为8),因此h会占用8个字节,有4个字节的内存被“浪费”了。

我们不会想浪费内存,最简单的策略就是讲数据域按照字节大小排序,先安排字节数大的域:

typedef struct{
    double     c;
    long long  g;
    char      *a;
    void      *h;
    float      e;
    short      b;
    char       d;
    char       f;
}S5;

S5的内存模型大致如下 :

总共占用32个字节的内存,减少了内存的浪费。

我们考虑这种情况 :

typedef struct{
    int k;
    S4  b;
}S6;

那么sizeof(S6) = ?,正确的结果是56。

实际上,编译器会把S4“展开”,它的声明可以看成是 :

typedef struct{
    int        k;
    /*S4展开*/
    char       *a;
    short      b;
    double     c;
    char       d;
    float      e;
    char       f;
    long long  g;
    void      *h;
}S6;

那么它的内存模型如下所示 :

这里写图片描述

最后的h多加4个字节是为了满足8字节内存对齐的原则。

你可能感兴趣的:(内存,结构,CSAPP)