單片機開發重點-字節對齊問題
在缺省情況下,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);
}
輸出結果:
結論如下:
- uint8_t數據類型全局變量 一字節對齊
- uint16_t數據類型全局變量 二字節對齊
- uint32_t數據類型全局變量 四字節對齊
- uint8_t數據類型局部變量 四字節對齊
- uint16_t數據類型局部變量 四字節對齊
- uint32_t數據類型局部變量 四字節對齊
注意
我在開發過程中有遇到一個問題
全局變量定義的是 uint32_t 的數據類型, 而該數組地址非四字節對齊, 在使用中強轉爲結構體指針, 再引用float成員導致字節未對齊問題, 打印查看到 float成員地址也非四字節對齊. 同時又產生了新的疑問: int 類型爲什麼沒有出錯, 而float卻出錯了呢? 是float有什麼特殊要求嗎?
這裏的新衍生的問題暫未可知, 但目前能確定的是 float 數據類型在未配置字節對齊時, 強制是 四字節對齊, 如果非四字節對齊, 系統會產生 HardFault 並進入 HardFault_Handler