使用`__attribute __((packed))`过度抽取结构包装警告?

时间:2016-11-16 19:44:23

标签: c gcc struct arm

我在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位臂上最佳组件定位所需要的。 奇怪的是,唯一发出/不发出警告的成员是overrunpadding_X成员,我不明白其原因。(好的, ARM docsByte 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整除的地址   分别

2 个答案:

答案 0 :(得分:4)

  

我认为在32位ARM上最佳组件定位需要32位对齐

是。

但你 32位对齐 [在最初问的问题中] because

  

packedstruct类型指定union属性等同于在每个结构或联合成员上指定packed属性。

given that

  

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]        

请注意,上面的r30x2001ef70,实际上是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]    

所以,非常清楚,我正在使用我原来的结构定义获得正确的对齐复制行为,尽管编译器显然错误地警告它会导致访问效率低下。