对内存对齐的理解
2014-09-30 11:17
288 查看
背景
由于工作需要整理了一份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执行次数。执行效率不高。
代码实践
规则
上面虽指定按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分配内存是否是对齐的(是对齐的,见分析过程和最后结论)
执行结果
分析结果
//本机默认字长是4字节
分析my_a_t类型
1). my_a_t类型指针变量首地址为 0x85ef008
2). 成员a首地址也为 0x85ef008。short自身占用2字节,填充2字节,地址到0x85ef00b,保证下一个地址是对齐的
3). 成员b首地址为 0x85ef00c(已经对齐)
4000
,占用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字节对齐
结果
指定16字节对齐
结果
Nginx源码中自行处理内存对齐的示例
总结
1、malloc分配的内存本身是对齐的,不需要程序员自己额外处理
2、如果是自己构建内存池,需要从已有内存池中使用已经分配的内存,并希望返回的内存地址是自然对齐的,可以参考nginx的做法来实现
由于工作需要整理了一份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(已经对齐)
4000
,占用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的做法来实现
相关文章推荐
- 深入理解C语言内存对齐
- Windows内存体系(6) -- 彻底理解内存对齐
- 最透彻的讲解结构体成员内存对齐问题——透彻理解哦
- C语言的内存的字节对齐的理解及其运用
- 深入理解内存对齐
- new/delete和malloc/free的区别,sizeof的理解和struct内存对齐
- 结构体变量对齐的理解,不同的对齐方式,造成不同的内存划分,从而满足不同平台的读写效率.
- 对内存对齐的深一步理解
- 内存对齐的方法理解
- (深入理解计算机系统)内存对齐
- 又是考查内存对齐和指针理解, 简单东西。
- 内存对齐问题的理解
- 对《C语言:内存字节对齐详解——struct 和 union 》的理解
- 对内存对齐的深一步理解
- 关于内存对齐的一点理解
- C++内存对齐的理解
- CPP--借助神器VS理解内存存储(含大小端对齐)
- c/c++中内存对齐完全理解
- 对内存对齐的深一步理解
- MDK环境的ARM汇编中内存对齐与对ADR Rd,{PC}+n形式的理解