單片機開發重點-字節對齊問題

單片機開發重點-字節對齊問題
在缺省情況下,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

相關鏈接:
漫談C變量——對齊 (1)
漫談C變量——對齊 (2)
漫談C變量——對齊 (3)
C語言字節對齊詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章