单片机开发重点-字节对齐问题

单片机开发重点-字节对齐问题
在缺省情况下,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语言字节对齐详解

你可能感兴趣的:(外功之嵌入式)