字节对齐与内存访问

来源:互联网 发布:使用weka进行数据挖掘 编辑:程序博客网 时间:2024/06/01 14:46
关于字节对齐,最早是在《高质量程序设计》中看到的,当时明白一点,就是因为定义的数据字节大小不一(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系统上,结构体也存在字节对齐问题,这个要注意。
不能轻易将一个结构体,按照连续的内存来转换。
除非我们已经十分清楚,结构体在内存中的对齐方式。


原创粉丝点击