我在32位ARM mcu(Atmel SAM4SD32C,Cortex-M4 / ARMv7E-M部件)上实现二进制日志记录系统,并且正在设计我的数据结构。我的目标是将日志格式描述为打包结构,并简单地将结构与char数组结合,以便写入日志设备(SD卡,通过FatFS,在这种情况下)。
基本上,我有一个非常简单的结构:
typedef struct adc_samples_t
{
int32_t adc_samples[6];
uint64_t acq_time;
int8_t overrun;
uint8_t padding_1;
uint8_t padding_2;
uint8_t padding_3;
} __attribute__((packed, aligned(4))) adc_sample_set;
现在,我的架构是32位,所以据我所知,访问任何成员/其他/然后overrun
成员应该是32位对齐,因此没有额外的开销。此外,aligned(4)
属性应该强制结构的任何实例化都在32位对齐的边界上。
但是,编译上面的结构定义会产生一堆警告:
In file included from ../src/main.c:13:0:
<snip>\src\fs\fs-logger.h(10,10): warning: packed attribute causes inefficient alignment for 'adc_samples' [-Wattributes]
int32_t adc_samples[6];
^
<snip>\src\fs\fs-logger.h(12,11): warning: packed attribute causes inefficient alignment for 'acq_time' [-Wattributes]
uint64_t acq_time;
据我所知(我现在意识到这是一个很大的假设),我认为32位对齐是32位臂上最佳组件定位所需要的。 奇怪的是,唯一发出/不发出警告的成员是(好的, ARM docs说overrun
和padding_X
成员,我不明白其原因。Byte accesses are always aligned.
)
到底发生了什么?我假设(可能不正确)结构实例化将在4字节边界上。编译器是否需要更宽的对齐(在8字节边界上)?
编辑:好的,深入研究ARM文档(这里的魔术词是&#34; Cortex-M4对齐&#34;:
3.3.5。地址对齐
对齐访问是一种操作,其中字对齐地址用于字,双字或多字访问,或者半字对齐地址用于半字访问。字节访问始终是对齐的。
Cortex-M4处理器仅支持以下指令的非对齐访问:
LDR,LDRT
LDRH,LDRHT
LDRSH,LDRSHT
STR,STRT
STRH,STRHT如果所有其他加载和存储指令执行未对齐访问,则会生成UsageFault异常,因此它们的访问必须是地址对齐的。有关UsageFaults的更多信息,请参阅故障处理。
未对齐访问通常比对齐访问慢。此外,某些内存区域可能不支持未对齐的访问。因此,ARM建议程序员确保访问是一致的。设陷 意外产生未对齐的访问,使用配置和控制寄存器中的UNALIGN_TRP位,参见配置和控制寄存器。
我的32位对齐值如何不是字对齐的?用户指南定义了&#34; Aligned&#34;如下:
对齐
存储在可被整除的地址的数据项 定义数据大小的字节数据说是对齐的。 对齐的单词和半字具有可被4整除的地址 和两个。词汇对齐和半字对齐 因此规定了可被4和2整除的地址 分别
答案 0 :(得分:4)
我认为在32位ARM上最佳组件定位需要32位对齐
是。
但你 32位对齐 [在最初问的问题中] because:
为
packed
和struct
类型指定union
属性等同于在每个结构或联合成员上指定packed
属性。
packed
属性指定变量或结构字段应具有最小可能的对齐 - 变量的一个字节和字段的一个位,除非您使用aligned
指定更大的值属性。
换句话说,如果你仍然想要一个压缩结构在你强制所有成员对齐之后仍然有一些最小对齐,因此类型本身没有任何东西,你需要指定 - 事实是实际上可能不会让-Wpacked
闭嘴是另一回事 - GCC可能会在实际考虑任何进一步的对齐修饰符之前反复吐出来。
请注意,就序列化而言,无论如何都不一定需要打包它。这些成员完全符合9个单词,因此任何位置的唯一编译器填充都是一个额外的单词,最后将总大小舍入到40个字节,因为acq_time
强制结构自然对齐为8.除非你想要要同时对这些东西进行操作,你可以简单地忽略它并将成员视为一个36字节的块。
答案 1 :(得分:2)
好的,在这一点上,我有点确信错误地发出警告。
我有一个静态定义的结构实例,并且在某一点上我把它归零:
adc_sample_set running_average;
int accumulated_samples;
inline void zero_average_buf(void)
{
accumulated_samples = 0;
running_average.adc_samples[0] = 0;
running_average.adc_samples[1] = 0;
running_average.adc_samples[2] = 0;
running_average.adc_samples[3] = 0;
running_average.adc_samples[4] = 0;
running_average.adc_samples[5] = 0;
running_average.overrun = 0;
running_average.acq_time = 0;
}
该功能的反汇编如下:
{
004005F8 push {r3, lr}
accumulated_samples = 0;
004005FA movs r2, #0
004005FC ldr r3, [pc, #36]
004005FE str r2, [r3]
running_average.adc_samples[0] = 0;
00400600 ldr r3, [pc, #36]
00400602 str r2, [r3]
running_average.adc_samples[1] = 0;
00400604 str r2, [r3, #4]
running_average.adc_samples[2] = 0;
00400606 str r2, [r3, #8]
running_average.adc_samples[3] = 0;
00400608 str r2, [r3, #12]
running_average.adc_samples[4] = 0;
0040060A str r2, [r3, #16]
running_average.adc_samples[5] = 0;
0040060C str r2, [r3, #20]
running_average.overrun = 0;
0040060E strb.w r2, [r3, #32]
running_average.acq_time = 0;
00400612 movs r0, #0
00400614 movs r1, #0
00400616 strd r0, r1, [r3, #24]
请注意,上面的r3
是0x2001ef70
,实际上是4字节对齐的。 r2
是字面值0
。
str
操作码需要 4字节对齐。 strd
操作码只需要4字节对齐,因为它看起来确实是两个连续的4字节操作,但我不知道它在内部是如何工作的。
如果我故意错误对齐我的结构,强制执行慢速路径复制操作:
typedef struct adc_samples_t
{
int8_t overrun;
int32_t adc_samples[6];
uint64_t acq_time;
uint8_t padding_1;
uint8_t padding_2;
uint8_t padding_3;
} __attribute__((packed, aligned(8))) adc_sample_set;
我得到以下程序集:
{
00400658 push {r3, lr}
accumulated_samples = 0;
0040065A movs r3, #0
0040065C ldr r2, [pc, #84]
0040065E str r3, [r2]
running_average.adc_samples[0] = 0;
00400660 ldr r2, [pc, #84]
00400662 strb r3, [r2, #1]
00400664 strb r3, [r2, #2]
00400666 strb r3, [r2, #3]
00400668 strb r3, [r2, #4]
running_average.adc_samples[1] = 0;
0040066A strb r3, [r2, #5]
0040066C strb r3, [r2, #6]
0040066E strb r3, [r2, #7]
00400670 strb r3, [r2, #8]
running_average.adc_samples[2] = 0;
00400672 strb r3, [r2, #9]
00400674 strb r3, [r2, #10]
00400676 strb r3, [r2, #11]
00400678 strb r3, [r2, #12]
running_average.adc_samples[3] = 0;
0040067A strb r3, [r2, #13]
0040067C strb r3, [r2, #14]
0040067E strb r3, [r2, #15]
00400680 strb r3, [r2, #16]
running_average.adc_samples[4] = 0;
00400682 strb r3, [r2, #17]
00400684 strb r3, [r2, #18]
00400686 strb r3, [r2, #19]
00400688 strb r3, [r2, #20]
running_average.adc_samples[5] = 0;
0040068A strb r3, [r2, #21]
0040068C strb r3, [r2, #22]
0040068E strb r3, [r2, #23]
00400690 strb r3, [r2, #24]
running_average.overrun = 0;
00400692 mov r1, r2
00400694 strb r3, [r1], #25
running_average.acq_time = 0;
00400698 strb r3, [r2, #25]
0040069A strb r3, [r1, #1]
0040069C strb r3, [r1, #2]
0040069E strb r3, [r1, #3]
004006A0 strb r3, [r1, #4]
004006A2 strb r3, [r1, #5]
004006A4 strb r3, [r1, #6]
004006A6 strb r3, [r1, #7]
所以,非常清楚,我正在使用我原来的结构定义获得正确的对齐复制行为,尽管编译器显然错误地警告它会导致访问效率低下。