字节对齐与内存访问
2016-03-20 21:19
281 查看
关于字节对齐,最早是在《高质量程序设计》中看到的,当时明白一点,就是因为定义的数据字节大小不一(1字节,2字节,4字节,8字节),在内存中可能会有字节对齐的操作,就是数据在内存中的排放,不一定是连续的。因此也产生了一个疑问,就是对于数组而言,特别是结构体,如果不连续排放了。那么,当我们将结构体,当做连续的一段内存来访问时,
是不是会造成错误呢?
当时,虽然有疑惑,但是一直没有特别明白的理解。
后来以为是在操作系统上,因为虚拟内存的缘故,虽然物理内存的存储不连续,但是经过映射还是会有连续的虚拟内存地址,这样访问的时候,这些操作,由操作系统帮我们修正了。
但是,如果把这种情况放在单片机上呢?怎么处理?于是,没有了答案。
以至于,很长一段时间,不想使用结构体,因为担心字节对齐造成错误。
后来一个同事说,结构体成员在内存中的排放是连续的。
而且在CSAPP上也看到了,The implementation of structures is similar that of arrays in that all of the components of a structure are stored in a contiguous region of memory.
但是有一点还是不解,
既然有对齐,而结构体成员有连续存放,那么中间的操作时怎么完成的。
就像上边提到的,一开始以为是由操作系统的虚拟内存机制做了,那么单片机上呢?
直到,注意到CSAPP上这句话,
As these examples show, the selection of the different fields of a structure is handled completely at compile time.
即,结构体成员的访问。
由于汇编语言没有数据类型的概念,那么在翻译成汇编文件时,一定是没有数据类型的,所以一定是编译器操作在汇编时,完成了这些操作。
在实现字节对齐之后,
结构体成员在内存中的访问,
共用体成员在内存中的访问,
程序中变量的访问,
都已经由编译器把偏移后的地址结算好了。
应该说,这里至少访问是没有问题了,但是有一个问题来了。既然,结构体是在由内存中的连续区域实现的,在保证了对齐后,如果在将这个结构体当做一个数组来访问,会出现什么情况呢?
当然,在Linux,Windows等操作系统上,通过虚拟内存机制,能够形成连续的虚拟内存。
但是在单片机上呢?
单片机上使用的都是实地址,当然现在慢慢都有MPU对于内存有了保护。
那么对于单片机上的C程序中的结构体的连续访问会出现什么问题呢?
struct S1{
int i;
char c;
int j;
};
0 4 5 9
+----+-+----+
| i |c| j |
+----+-+----+
0 4 5 8 12
+----+-+---+----+
| i |c| | j |
+----+-+---+----+
明天去测试下,
在CM3上。
在编程时,不同体系结构的CPU对于字节对齐都有一定的要求。
对于ARM7而言,如果是不对其访问,就会造成读取数据错误。
对于有些CPU特别是RICS架构,不支持非对齐访问。
ARM7要求是4bytes对齐。
Intel虽然,非对齐访问不会出错,但是会造成执行效率的降低。
加入对齐访问可以由一次内存访问,则非对齐访问,需要两个内存访问才能完成。
因此它推荐采用字节对齐,以提升内存系统的性能。
struct testStruct{
uint8_t i;
uint32_t j;
uint8_t l;
uint32_t k;
};
memset在Keil下的汇编语句
146: memset(&a, 0, sizeof(struct testStruct));
0x08009176 2000 MOVS r0,#0x00
0x08009178 9002 STR r0,[sp,#0x08]
0x0800917A 9003 STR r0,[sp,#0x0C]
0x0800917C 9004 STR r0,[sp,#0x10]
0x0800917E 9005 STR r0,[sp,#0x14]
148: memset(&a, 0x63, sizeof(struct testStruct));
0x08009176 2263 MOVS r2,#0x63
0x08009178 2110 MOVS r1,#0x10
0x0800917A A806 ADD r0,sp,#0x18
0x0800917C F7F7FD38 BL.W __aeabi_memset (0x08000BF0)
将memset会变为STR语句在这里是4bytes对齐的。
memcpy在Keil下的汇编语句应该是在程序中,有一个memcpy的库文件。
151: memcpy(&b, (void *)&a, a.i);
0x08009196 F89D2018 LDRB r2,[sp,#0x18]
0x0800919A A906 ADD r1,sp,#0x18
0x0800919C A802 ADD r0,sp,#0x08
0x0800919E F7F7FCF5 BL.W __aeabi_memcpy4 (0x08000B8C)
struct testStruct{
uint8_t i;
uint32_t j;
uint8_t l;
uint32_t k;
};
volatile uint32_t t1;
volatile uint8_t t2;
volatile uint8_t p[20];
memset(&a, 0x63, sizeof(struct testStruct));
a.i = sizeof(struct testStruct);
a.j = 0x68;
a.l = 0x14;
a.k = 0x81;
a = a;
memcpy(p, (void*)&a, a.i);
memcpy(&b, (void *)&a, a.i);
volatile struct testStruct a;
struct testStruct b;
在单片机中,不能直接将结构体按照连续内存排列来理解。
否则,会出现意想不到的问题。
/*----------------------------------------------------------------------------------------------------------*/
struct Str{
5 unsigned int j;
6 unsigned int k;
7 unsigned char l;
8 unsigned char i;
9 };
10 struct Str a;
11 unsigned char p[20];
(gdb) p &p
$1 = (unsigned char (*)[20]) 0x804971c
(gdb) x /32 0x804971c
0x804971c <p>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804972c <p+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804973c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) s
18 j = sizeof(struct Str);
(gdb) p j
$2 = 0 '\0'
(gdb) s
19 a.i = 0x12;
(gdb) p j
$3 = 12 '\f'
(gdb) s
20 a.j = 0x21;
(gdb)
21 a.l = 0x68;
(gdb)
23 memcpy(p, &a, j);
(gdb)
24 for(i = 0; i < j; i++ )
(gdb) x /32 0x804971c
0x804971c <p>: 0x00000021 0x63636363 0x63631268 0x00000000
0x804972c <p+16>: 0x00000000 0x00000021 0x63636363 0x63631268
0x804973c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
/*----------------------------------------------------------------------------------------------------------*/
4 struct Str{
5 unsigned char i;
6 unsigned int j;
7 unsigned char l;
8 unsigned int k;
9 };
10 struct Str a;
11 unsigned char p[20];
(gdb) p &p
$1 = (unsigned char (*)[20]) 0x804971c
(gdb) x /32 0x804971c
0x804971c <p>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804972c <p+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804973c <a+12>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) s
18 j = sizeof(struct Str);
(gdb) p j
$2 = 0 '\0'
(gdb) s
19 a.i = 0x12;
(gdb) p j
$3 = 16 '\020'
(gdb) s
20 a.j = 0x21;
(gdb)
21 a.l = 0x68;
(gdb)
23 memcpy(p, &a, j);
(gdb)
24 for(i = 0; i < j; i++ )
(gdb)
26 printf("%2x ", p[i]);
(gdb)
24 for(i = 0; i < j; i++ )
(gdb) x /32 0x804971c
0x804971c <p>: 0x63636312 0x00000021 0x63636368 0x63636363
0x804972c <p+16>: 0x00000000 0x63636312 0x00000021 0x63636368
0x804973c <a+12>: 0x63636363 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
/*----------------------------------------------------------------------------------------------------------*/
经测试,即使是在Linux系统上,结构体也存在字节对齐问题,这个要注意。
不能轻易将一个结构体,按照连续的内存来转换。
除非我们已经十分清楚,结构体在内存中的对齐方式。
是不是会造成错误呢?
当时,虽然有疑惑,但是一直没有特别明白的理解。
后来以为是在操作系统上,因为虚拟内存的缘故,虽然物理内存的存储不连续,但是经过映射还是会有连续的虚拟内存地址,这样访问的时候,这些操作,由操作系统帮我们修正了。
但是,如果把这种情况放在单片机上呢?怎么处理?于是,没有了答案。
以至于,很长一段时间,不想使用结构体,因为担心字节对齐造成错误。
后来一个同事说,结构体成员在内存中的排放是连续的。
而且在CSAPP上也看到了,The implementation of structures is similar that of arrays in that all of the components of a structure are stored in a contiguous region of memory.
但是有一点还是不解,
既然有对齐,而结构体成员有连续存放,那么中间的操作时怎么完成的。
就像上边提到的,一开始以为是由操作系统的虚拟内存机制做了,那么单片机上呢?
直到,注意到CSAPP上这句话,
As these examples show, the selection of the different fields of a structure is handled completely at compile time.
即,结构体成员的访问。
由于汇编语言没有数据类型的概念,那么在翻译成汇编文件时,一定是没有数据类型的,所以一定是编译器操作在汇编时,完成了这些操作。
在实现字节对齐之后,
结构体成员在内存中的访问,
共用体成员在内存中的访问,
程序中变量的访问,
都已经由编译器把偏移后的地址结算好了。
应该说,这里至少访问是没有问题了,但是有一个问题来了。既然,结构体是在由内存中的连续区域实现的,在保证了对齐后,如果在将这个结构体当做一个数组来访问,会出现什么情况呢?
当然,在Linux,Windows等操作系统上,通过虚拟内存机制,能够形成连续的虚拟内存。
但是在单片机上呢?
单片机上使用的都是实地址,当然现在慢慢都有MPU对于内存有了保护。
那么对于单片机上的C程序中的结构体的连续访问会出现什么问题呢?
struct S1{
int i;
char c;
int j;
};
0 4 5 9
+----+-+----+
| i |c| j |
+----+-+----+
0 4 5 8 12
+----+-+---+----+
| i |c| | j |
+----+-+---+----+
明天去测试下,
在CM3上。
在编程时,不同体系结构的CPU对于字节对齐都有一定的要求。
对于ARM7而言,如果是不对其访问,就会造成读取数据错误。
对于有些CPU特别是RICS架构,不支持非对齐访问。
ARM7要求是4bytes对齐。
Intel虽然,非对齐访问不会出错,但是会造成执行效率的降低。
加入对齐访问可以由一次内存访问,则非对齐访问,需要两个内存访问才能完成。
因此它推荐采用字节对齐,以提升内存系统的性能。
struct testStruct{
uint8_t i;
uint32_t j;
uint8_t l;
uint32_t k;
};
memset在Keil下的汇编语句
146: memset(&a, 0, sizeof(struct testStruct));
0x08009176 2000 MOVS r0,#0x00
0x08009178 9002 STR r0,[sp,#0x08]
0x0800917A 9003 STR r0,[sp,#0x0C]
0x0800917C 9004 STR r0,[sp,#0x10]
0x0800917E 9005 STR r0,[sp,#0x14]
148: memset(&a, 0x63, sizeof(struct testStruct));
0x08009176 2263 MOVS r2,#0x63
0x08009178 2110 MOVS r1,#0x10
0x0800917A A806 ADD r0,sp,#0x18
0x0800917C F7F7FD38 BL.W __aeabi_memset (0x08000BF0)
将memset会变为STR语句在这里是4bytes对齐的。
memcpy在Keil下的汇编语句应该是在程序中,有一个memcpy的库文件。
151: memcpy(&b, (void *)&a, a.i);
0x08009196 F89D2018 LDRB r2,[sp,#0x18]
0x0800919A A906 ADD r1,sp,#0x18
0x0800919C A802 ADD r0,sp,#0x08
0x0800919E F7F7FCF5 BL.W __aeabi_memcpy4 (0x08000B8C)
struct testStruct{
uint8_t i;
uint32_t j;
uint8_t l;
uint32_t k;
};
volatile uint32_t t1;
volatile uint8_t t2;
volatile uint8_t p[20];
memset(&a, 0x63, sizeof(struct testStruct));
a.i = sizeof(struct testStruct);
a.j = 0x68;
a.l = 0x14;
a.k = 0x81;
a = a;
memcpy(p, (void*)&a, a.i);
memcpy(&b, (void *)&a, a.i);
volatile struct testStruct a;
struct testStruct b;
在单片机中,不能直接将结构体按照连续内存排列来理解。
否则,会出现意想不到的问题。
/*----------------------------------------------------------------------------------------------------------*/
struct Str{
5 unsigned int j;
6 unsigned int k;
7 unsigned char l;
8 unsigned char i;
9 };
10 struct Str a;
11 unsigned char p[20];
(gdb) p &p
$1 = (unsigned char (*)[20]) 0x804971c
(gdb) x /32 0x804971c
0x804971c <p>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804972c <p+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804973c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) s
18 j = sizeof(struct Str);
(gdb) p j
$2 = 0 '\0'
(gdb) s
19 a.i = 0x12;
(gdb) p j
$3 = 12 '\f'
(gdb) s
20 a.j = 0x21;
(gdb)
21 a.l = 0x68;
(gdb)
23 memcpy(p, &a, j);
(gdb)
24 for(i = 0; i < j; i++ )
(gdb) x /32 0x804971c
0x804971c <p>: 0x00000021 0x63636363 0x63631268 0x00000000
0x804972c <p+16>: 0x00000000 0x00000021 0x63636363 0x63631268
0x804973c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
/*----------------------------------------------------------------------------------------------------------*/
4 struct Str{
5 unsigned char i;
6 unsigned int j;
7 unsigned char l;
8 unsigned int k;
9 };
10 struct Str a;
11 unsigned char p[20];
(gdb) p &p
$1 = (unsigned char (*)[20]) 0x804971c
(gdb) x /32 0x804971c
0x804971c <p>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804972c <p+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804973c <a+12>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) s
18 j = sizeof(struct Str);
(gdb) p j
$2 = 0 '\0'
(gdb) s
19 a.i = 0x12;
(gdb) p j
$3 = 16 '\020'
(gdb) s
20 a.j = 0x21;
(gdb)
21 a.l = 0x68;
(gdb)
23 memcpy(p, &a, j);
(gdb)
24 for(i = 0; i < j; i++ )
(gdb)
26 printf("%2x ", p[i]);
(gdb)
24 for(i = 0; i < j; i++ )
(gdb) x /32 0x804971c
0x804971c <p>: 0x63636312 0x00000021 0x63636368 0x63636363
0x804972c <p+16>: 0x00000000 0x63636312 0x00000021 0x63636368
0x804973c <a+12>: 0x63636363 0x00000000 0x00000000 0x00000000
0x804974c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804975c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804976c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804977c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804978c: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
/*----------------------------------------------------------------------------------------------------------*/
经测试,即使是在Linux系统上,结构体也存在字节对齐问题,这个要注意。
不能轻易将一个结构体,按照连续的内存来转换。
除非我们已经十分清楚,结构体在内存中的对齐方式。
相关文章推荐
- CF651B-Beautiful Paintings
- eclipse 是用来写客户端的,MyEclipse 是用来写服务器端的,谐音记忆法,My 买,买服务器这样就好记了。
- 看懂redis的配置
- hdu2699 Five in a Row 暴力穷举
- Git中三种文件状态及其转换(转)
- python爬虫 前程无忧网页抓取
- 数据库-mongodb-聚合与map reduce
- 对于是否需要有代码规范,请考虑下列论点并反驳/支持:
- [C/C++] C/C++延伸学习系列之STL及Boost库概述
- 1006 elevator reach time
- php环境的搭建
- 面向对象上-1
- 菜鸟如何使用Hanlp
- 2.1-kvm恢复和删除快照
- Pinot中的Dictionary Index源码分析
- python之SQLAlchemy
- 校第十六届大学生程序设计竞赛暨2016省赛集训队选拔赛1002
- Python笔记(4)——Python Web框架
- python 面向对象编程学习
- vs2013+opencv2.4.11+Qt5.5.1配置