您的位置:首页 > 其它

字节对齐与内存访问

2013-02-05 09:47 387 查看
关于字节对齐,最早是在《高质量程序设计》中看到的,当时明白一点,就是因为定义的数据字节大小不一(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系统上,结构体也存在字节对齐问题,这个要注意。
不能轻易将一个结构体,按照连续的内存来转换。
除非我们已经十分清楚,结构体在内存中的对齐方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: