对内存对齐的理解

来源:互联网 发布:ubuntu rar 解压命令 编辑:程序博客网 时间:2024/04/29 04:47

背景

由于工作需要整理了一份C面试题,其中涉及了内存对齐概念,通过查阅一些资料和自行编写代码验证,来加深对它的理解,现将查阅过程整理的资料和心得备忘如下。


本机所有实验操作在如下环境下验证

Linux ubuntu 3.2.0-67-generic #101-Ubuntu SMP Tue Jul 15 17:45:51 UTC 2014 i686 i686 i386 GNU/Linux
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3


内存对齐、结构体内存对齐

1、内存对齐是指变量首地址对齐,非每个变量大小对齐
2、结构体内存对齐,要求结构体内每一个成员都是内存对齐
3、结构体数组,要求每一个结构体对象内存对齐


自然对齐

一个数据的地址是它类型长度的整数倍,这个数据就是"自然对齐"


内存对齐的原因给出一个例子解释)


存在一个int型变量i:int i;

假设地址是 0x00000004
int类型长度:sizeof(int) == 4
地址 0x00000004是4的整数倍,那么变量i是
自然对齐的


cpu存取是按照字长的整数倍存取,假如字长是4,则按照 0x00000000 和 0x00000004 进行存取

取上述变量i时,通过一次cpu就可以读取到数据。即从地址0x00000004读取一个字长,读取的结果囊括了i变量占用的四个字节。

如果上述变量i的地址是 0x00000001, 则存储变量i要占用4个字节,地址分别是:

0x00000001
0x00000002
0x00000003
0x00000004

cpu需要从 0x00000000地址读取一个字长,从0x00000004地址再读取一个字长, 将两部分合并,才能囊括存储上述变量i的四个地址。即从0x00000001地址开始读取4个字节获取变量i的数值,需要两次cpu执行次数。执行效率不高。


代码实践

#pragma pack(8)struct s1{    short a;    long b;};struct s2{    short c;    s1 d;    long long e;};#pragma pack()

规则

上面虽指定按8字节对齐,但并不是所有的成员都以8字节对齐。每个成员按自己的方式对齐。规则如下:


1、每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(#pragma pack(8))中较小的一个对齐。
2、结构的长度必须为所用过的所有对齐参数的整数倍(只要是最大的对齐参数的整数倍即可),不够就补空字节
3、结构体对齐方式,是该结构定义(声明)时它的所有成员使用的对齐参数中最大的一个


分析过程

s1中
a). 成员a是2字节,默认按2字节对齐,指定对齐参数为8,这两个值中取2,a按2字节对齐
b). 成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,a后补2个字节后存放b
c). 此时结构体长度为8,8是4的倍数,满足上述的第3条规则。所以sizeof(s1)=8


s2中
a). c和s1中的a一样,按2字节对齐
b). d是个结构,大小是8个字节,按照规则,它的默认对齐方式就是该结构定义(声明)时它的所有成员使用的对齐参数中最大的一个,s1的是4,小于指定的8。成员d就是按4字节对齐,c后补2个字节,后面是8个字节的结构体d。
c). 成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以d后又补上4个字节,从第16个字节开始放置成员e。
d). 此时总长度为24,可以被最大对齐参数8(成员e按8字节对齐)整除。所以sizeof(s2)=24


日常malloc分配内存是否是对齐的(是对齐的,见分析过程和最后结论)

<pre name="code" class="cpp">typedef struct my_a_s        my_a_t;typedef struct my_b_s        my_b_t;struct my_a_s {    short a;    long long  b;      int   c;  };struct my_b_s {    long a;    my_a_t s;    short c;    char *d; };void struct_align_test() {    my_a_t    *a;    my_b_t      *p;    size_t       len;    len = sizeof(my_a_t);    printf("size of my_a_t type is: %d\n", len);    a = malloc(len);        printf("my_a_t type pointer 's head address: %p\n", a);    printf("my_a_t type pointer 's member a 's address: %p\n", &a->a);    printf("my_a_t type pointer 's member b 's address: %p\n", &a->b);    printf("my_a_t type pointer 's member c 's address: %p\n", &a->c);    free(a);    len = sizeof(my_b_t);    printf("size of my_b_t type is : %d\n", len);    p = malloc(len);    printf("my_b_t type pointer 's head address: %p\n", p);    printf("my_b_t type pointer 's member a 's address: %p\n", &p->a);    printf("my_b_t type pointer 's member s 's address: %p\n", &p->s);    printf("my_b_t type pointer 's member c 's address: %p\n", &p->c);    printf("my_b_t type pointer 's member d 's address: %p\n", &p->d);    free(p);}

执行结果

size of my_a_t type is: 16my_a_t type pointer 's head address: 0x85ef008my_a_t type pointer 's member a 's address: 0x85ef008my_a_t type pointer 's member b 's address: 0x85ef00cmy_a_t type pointer 's member c 's address: 0x85ef014size of my_b_t type is : 28my_b_t type pointer 's head address: 0x85ef020my_b_t type pointer 's member a 's address: 0x85ef020my_b_t type pointer 's member s 's address: 0x85ef024my_b_t type pointer 's member c 's address: 0x85ef034my_b_t type pointer 's member d 's address: 0x85ef038

分析结果

//本机默认字长是4字节
分析my_a_t类型
1). my_a_t类型指针变量首地址为 0x85ef008
2). 成员a首地址也为 0x85ef008。short自身占用2字节,填充2字节,地址到0x85ef00b,保证下一个地址是对齐的
3). 成员b首地址为 0x85ef00c(已经对齐),占用8字节后,地址到 0x85ef013
4). 成员c首地址为 0x85ef014(已经对齐),占用4个字节,不再需要填充
5). 结构体总长度为16(4+8+4)

分析my_b_t类型

1). my_b_t类型指针变量首地址为 0x85ef020
2). 成员a首地址也为 0x85ef020。本身已对齐,long自身占用4字节,不需要填充,地址到0x85ef023,保证下一个地址是对齐的
3). 成员s首地址为 0x85ef024(已经对齐),占用16字节后(my_a_t类型总长度),地址到 0x85ef033
4). 成员c首地址为 0x85ef034(已经对齐),short自身占用2字节,需要补齐2字节,地址到 0x85ef037
5). 成员d首地址为 0x85ef038(已经对齐),char *自身占用4字节,不需要补充
5). 结构体总长度为28(4+16+4+4)


nginx中对齐处理

#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */

#define ngx_align_ptr(p, a)                                                   \

    (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

指定4字节对齐

char *m; char *pool;pool = malloc(4096);printf("pool 's address: %p\n", pool);//这里按照4字节对齐m = ngx_align_ptr(pool, 4); printf("m 's address: %p\n", m); free(pool);


结果

word size: 4pool 's address: 0x8b8e008//pool 的地址已经按照4字节对齐,所以m的地址不变m 's address: 0x8b8e008

指定16字节对齐
char *m; char *pool;pool = malloc(4096);printf("pool 's address: %p\n", pool);//这里按照16字节对齐m = ngx_align_ptr(pool, 16); printf("m 's address: %p\n", m); free(pool);

结果

word size: 4pool 's address: 0x96f6008//pool 的地址不是按照16字节对齐,所以返回的m地址在pool的基础上增加了8个字节//意味着从0x96f6008到 0x96f600f的地址被跳过了//但是在释放已经分配空间时,必须要free整个pool池m 's address: 0x96f6010

Nginx源码中自行处理内存对齐的示例

void *ngx_palloc(ngx_pool_t *pool, size_t size){    u_char      *m;    ngx_pool_t  *p;    if (size <= pool->max) {        p = pool->current;        do {    //这里是按照 NGX_ALIGNMENT (字长)对齐的    //保证了 p->d.last 指向的地址是按照字长对齐的            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);            if ((size_t) (p->d.end - m) >= size) {                p->d.last = m + size;                return m;            }            p = p->d.next;        } while (p);        return ngx_palloc_block(pool, size);    }    return ngx_palloc_large(pool, size);}


总结

1、malloc分配的内存本身是对齐的,不需要程序员自己额外处理

2、如果是自己构建内存池,需要从已有内存池中使用已经分配的内存,并希望返回的内存地址是自然对齐的,可以参考nginx的做法来实现



0 0
原创粉丝点击