对内存对齐的理解

背景

由于工作需要整理了一份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: 16
my_a_t type pointer 's head address: 0x85ef008
my_a_t type pointer 's member a 's address: 0x85ef008	
my_a_t type pointer 's member b 's address: 0x85ef00c
my_a_t type pointer 's member c 's address: 0x85ef014
size of my_b_t type is : 28
my_b_t type pointer 's head address: 0x85ef020
my_b_t type pointer 's member a 's address: 0x85ef020
my_b_t type pointer 's member s 's address: 0x85ef024
my_b_t type pointer 's member c 's address: 0x85ef034
my_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: 4
pool '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: 4
pool '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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值