使用 `__attribute__((packed))` 过度渴望结构打包警告?

Fak*_*ame 5 c gcc struct arm

我正在 32 位 ARM mcu(Atmel SAM4SD32C,Cortex-M4/ARMv7E-M 部件)上实现二进制日志系统,并且正在设计我的数据结构。我的目标是将日志格式描述为打包结构,并简单地将结构与字符数组结合起来,用于写入日志设备(在本例中是通过 FatFS 的 SD 卡)。

基本上,我有一个非常简单的结构:

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;
Run Code Online (Sandbox Code Playgroud)

现在,我的架构是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;
Run Code Online (Sandbox Code Playgroud)

据我所知(我现在意识到这是一个很大的假设),我假设 32 位对齐是在 32 位臂上实现最佳组件定位所需的全部内容。奇怪的是,唯一/不/产生警告的成员是overrunpadding_X成员,我不明白其原因。(好吧,ARM 文档Byte accesses are always aligned.

这里究竟发生了什么?我假设(可能是错误的)结构实例化将在 4 字节边界上。编译器是否需要更广泛的对齐(在 8 字节边界上)?


编辑:好的,深入研究 ARM 文档(这里的神奇词是“Cortex-M4 对齐”:

3.3.5. 地址对齐

对齐访问是一种操作,其中字对齐地址用于字、双字或多字访问,或者半字对齐地址用于半字访问。字节访问始终是对齐的。

Cortex-M4 处理器仅支持以下指令的未对齐访问:

LDR、LDRT
LDRH、LDRHT
LDRSH、LDRSHT
STR、STRT
STRH、STRHT

所有其他加载和存储指令如果执行未对齐的访问,则会生成UsageFault异常,因此它们的访问必须是地址对齐的。有关UsageFaults 的更多信息,请参阅故障处理。

未对齐的访问通常比对齐的访问慢。此外,某些内存区域可能不支持未对齐访问。因此,ARM 建议程序员确保访问对齐。要捕获意外生成的未对齐访问,请使用配置和控制寄存器中的 UNALIGN_TRP 位,请参阅配置和控制寄存器。

我的 32 位对齐值为何不是字对齐的?用户指南将“对齐”定义如下:

对齐
存储在可被定义数据大小的字节数整除的地址处的数据项称为对齐的。对齐字和半字的地址分别可被四和二整除。因此,术语“字对齐”和“半字对齐”规定分别可被四和二整除的地址。

Fak*_*ame 3

好吧,在这一点上,我有点确信警告是错误发出的。

我有一个静态定义的结构实例,并且在某一时刻我将其归零:

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;

}
Run Code Online (Sandbox Code Playgroud)

该函数的反汇编如下:

{
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]        
Run Code Online (Sandbox Code Playgroud)

注意r3上面的 is 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;
Run Code Online (Sandbox Code Playgroud)

我得到以下程序集:

{
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]    
Run Code Online (Sandbox Code Playgroud)

因此,很明显,我通过原始结构定义获得了正确的对齐复制行为,尽管编译器显然错误地警告这将导致访问效率低下。