单片机开发重点-字节对齐问题
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
#pragma pack(n)
,C编译器将按照n个字节对齐。#pragma pack()
,取消自定义字节对齐方式。另外,还有如下的一种方式:
__attribute((aligned (n)))
,让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
__attribute__((packed))
,取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
第三种方式:
__attribute__((packed))
,强制按照一字节对齐方式。
附上cmsis_armcc.h
/* CMSIS compiler specific defines */
#ifndef __ASM
#define __ASM __asm
#endif
#ifndef __INLINE
#define __INLINE __inline
#endif
#ifndef __STATIC_INLINE
#define __STATIC_INLINE static __inline
#endif
#ifndef __STATIC_FORCEINLINE
#define __STATIC_FORCEINLINE static __forceinline
#endif
#ifndef __NO_RETURN
#define __NO_RETURN __declspec(noreturn)
#endif
#ifndef __USED
#define __USED __attribute__((used))
#endif
#ifndef __WEAK
#define __WEAK __attribute__((weak))
#endif
#ifndef __PACKED
#define __PACKED __attribute__((packed))
#endif
#ifndef __PACKED_STRUCT
#define __PACKED_STRUCT __packed struct
#endif
#ifndef __PACKED_UNION
#define __PACKED_UNION __packed union
#endif
#ifndef __UNALIGNED_UINT32 /* deprecated */
#define __UNALIGNED_UINT32(x) (*((__packed uint32_t *)(x)))
#endif
#ifndef __UNALIGNED_UINT16_WRITE
#define __UNALIGNED_UINT16_WRITE(addr, val) ((*((__packed uint16_t *)(addr))) = (val))
#endif
#ifndef __UNALIGNED_UINT16_READ
#define __UNALIGNED_UINT16_READ(addr) (*((const __packed uint16_t *)(addr)))
#endif
#ifndef __UNALIGNED_UINT32_WRITE
#define __UNALIGNED_UINT32_WRITE(addr, val) ((*((__packed uint32_t *)(addr))) = (val))
#endif
#ifndef __UNALIGNED_UINT32_READ
#define __UNALIGNED_UINT32_READ(addr) (*((const __packed uint32_t *)(addr)))
#endif
#ifndef __ALIGNED
#define __ALIGNED(x) __attribute__((aligned(x)))
#endif
#ifndef __RESTRICT
#define __RESTRICT __restrict
#endif
这三种方式, 是有细微差别, 至于具体信息,可以查看各编译器文档描述中对其解释。我有查阅过ARMCC编译器对其解释,只知道有差别,但具体是什么,还待研究
测试代码如下
static uint8_t GlobalTmp1[] = {0};
static uint8_t GlobalTmp2[] = {0, 1};
static uint8_t GlobalTmp3[] = {0, 1, 2};
static uint8_t GlobalTmp4[] = {0, 1, 2, 3};
uint8_t GlobalTmp21[] = {0};
uint8_t GlobalTmp22[] = {0, 1};
uint8_t GlobalTmp23[] = {0, 1, 2};
uint8_t GlobalTmp24[] = {0, 1, 2, 3};
int main(void)
{
printf("GlobalTmp1: %p; GlobalTmp2: %p; GlobalTmp3: %p; GlobalTmp4: %p;\r\n"
, &GlobalTmp1, &GlobalTmp2, &GlobalTmp3, &GlobalTmp4);
printf("GlobalTmp21: %p; GlobalTmp22: %p; GlobalTmp23: %p; GlobalTmp24: %p;\r\n"
, &GlobalTmp21, &GlobalTmp22, &GlobalTmp23, &GlobalTmp24);
uint8_t tmp1[] = {0};
uint8_t tmp2[] = {0, 1};
uint8_t tmp3[] = {0, 1, 2};
uint8_t tmp4[] = {0, 1, 2, 3};
printf("tmp1: %p; tmp2: %p; tmp3: %p; tmp4: %p\r\n", &tmp1, &tmp2, &tmp3, &tmp4);
}
注意
我在开发过程中有遇到一个问题
全局变量定义的是 uint32_t 的数据类型, 而该数组地址非四字节对齐, 在使用中强转为结构体指针, 再引用float成员导致字节未对齐问题, 打印查看到 float成员地址也非四字节对齐. 同时又产生了新的疑问: int 类型为什么没有出错, 而float却出错了呢? 是float有什么特殊要求吗?
这里的新衍生的问题暂未可知, 但目前能确定的是 float 数据类型在未配置字节对齐时, 强制是 四字节对齐, 如果非四字节对齐, 系统会产生 HardFault 并进入 HardFault_Handler
相关链接:
漫谈C变量——对齐 (1)
漫谈C变量——对齐 (2)
漫谈C变量——对齐 (3)
C语言字节对齐详解