繁体   English   中英

16 位的位带结构给出了 3 个字节的大小以及不正确的分配

[英]Bit banded struct of 16 bits gives size as 3 bytes as well as incorrect assignments

我目前正在为 STM32 MCU 编写代码以与外设一起工作,为了更轻松地访问单个位,我使用了位带结构,如下所示:

typedef struct
{
  uint8_t ch3_unreadconv : 1; // 0
  uint8_t ch2_unreadconv : 1; // 1
  uint8_t ch1_unreadconv : 1; // 2
  uint8_t ch0_unreadconv : 1; // 3
  uint8_t not_used_01    : 2; // 4-5
  uint8_t drdy           : 1; // 6
  uint8_t not_used_02    : 2; // 7-8
  uint8_t err_alw        : 1; // 9
  uint8_t err_ahw        : 1; // 10
  uint8_t err_wd         : 1; // 11
  uint8_t not_used_03    : 2; // 12-13
  uint8_t err_chan       : 2; // 14-15
} fdc_status_reg_t;
fdc_status_reg_t statusReg;

// Assign value 13 to the register.
*((uint16_t*) &statusReg) = 0xD;

但是当我做sizeof(statusReg); 我得到3作为答案。 当我使用代码为整个寄存器赋值时,除了最后 2 位,即err_chan外,其他位都设置正确。 我已经在 GCC 上尝试了相同的方法并得到了类似的结果来测试它是否是 STM 特定的问题。 经过进一步调查,我发现将第 7 位和第 8 位设置为两个单独的位似乎可以解决问题,即,

typedef struct
 {
     uint8_t ch3_unreadconv : 1; // 0
     uint8_t ch2_unreadconv : 1; // 1
     uint8_t ch1_unreadconv : 1; // 2
     uint8_t ch0_unreadconv : 1; // 3
     uint8_t not_used_01    : 2; // 4-5
     uint8_t drdy           : 1; // 6
     uint8_t not_used_02    : 1; // 7
     uint8_t not_used_02_01 : 1; // 8
     uint8_t err_alw        : 1; // 9
     uint8_t err_ahw        : 1; // 10
     uint8_t err_wd         : 1; // 11
     uint8_t not_used_03    : 2; // 12-13
     uint8_t err_chan       : 2; // 14-15
 } fdc_status_reg_t;

sizeof提供 2 个字节的正确输出。 并且还给出了分配的预测行为。 这(对我来说)看起来像是填充/内存对齐问题,但我不明白是怎么回事,每当在第 7 位和第 8 位使用uint8_t时我都能够重新创建它,但是当跨 7-8 位使用uint16_t时效果很好. 请就可能出现的问题提出建议,并且可能比在第 7 位拆分位更好的解决方法。 提前致谢。

第一个结构体中的字节数与此字段成员的字节对齐边界相撞:

uint8_t not_used_02    : 2; // 7-8

这导致此时位计数为 9,而不是 8,因此调用位字段 padding ,通过添加一个字节来容纳额外的位,使字节计数为 3。

如果您可以调整位字段结构中的字段顺序,则可以将字节数减少到 2。或者...

对于位域,系统实现不能保证在系统与系统之间保持一致,但您可以对实现进行试验以获得所需的结果。 例如,尝试使用足够大的type来容纳所有位,例如uint16_t 在我的系统上,使用uint16_t导致具有相同字段顺序的相同字段的sizeof 2:

typedef struct
{
  uint16_t ch3_unreadconv : 1; // 0
  uint16_t ch2_unreadconv : 1; // 1
  uint16_t ch1_unreadconv : 1; // 2
  uint16_t ch0_unreadconv : 1; // 3
  uint16_t not_used_01    : 2; // 4-5
  uint16_t drdy           : 1; // 6
  uint16_t not_used_02    : 2; // 7-8
  uint16_t err_alw        : 1; // 9
  uint16_t err_ahw        : 1; // 10
  uint16_t err_wd         : 1; // 11
  uint16_t not_used_03    : 2; // 12-13
  uint16_t err_chan       : 2; // 14-15
} fdc_status_reg_t;
fdc_status_reg_t statusReg;

我建议为结构使用正确的整数大小:

typedef struct
 {
     uint16_t ch3_unreadconv : 1; // 0
     uint16_t ch2_unreadconv : 1; // 1
     uint16_t ch1_unreadconv : 1; // 2
     uint16_t ch0_unreadconv : 1; // 3
     uint16_t                : 2; // 4-5
     uint16_t drdy           : 1; // 6
     uint16_t                : 1; // 7
     uint16_t                : 1; // 8
     uint16_t err_alw        : 1; // 9
     uint16_t err_ahw        : 1; // 10
     uint16_t err_wd         : 1; // 11
     uint16_t                : 2; // 12-13
     uint16_t err_chan       : 2; // 14-15
 } fdc_status_reg_t;

您不需要未使用的字段名称

您还可以打包结构

 typedef struct
 {
     uint8_t ch3_unreadconv : 1; // 0
     uint8_t ch2_unreadconv : 1; // 1
     uint8_t ch1_unreadconv : 1; // 2
     uint8_t ch0_unreadconv : 1; // 3
     uint8_t                : 2; // 4-5
     uint8_t drdy           : 1; // 6
     uint8_t                : 1; // 7
     uint8_t                : 1; // 8
     uint8_t err_alw        : 1; // 9
     uint8_t err_ahw        : 1; // 10
     uint8_t err_wd         : 1; // 11
     uint8_t                : 2; // 12-13
     uint8_t err_chan       : 2; // 14-15
 } __attribute__((packed)) fdc_status_reg_t1;

这里有一个例子:

https://godbolt.org/z/xUhUT-

将类型更改为uint16_t以“忽略” 7-8字节处的字节边界。

下面1.c源文件,当编译arm-none-eabi-gdb 9.1和下simlator exeucted arm-none-eabi-gdb ,输出2sizeof

$ cat 1.c
#include <stdint.h>
#include <stdio.h>
typedef struct
{
  uint16_t ch3_unreadconv : 1; // 0
  uint16_t ch2_unreadconv : 1; // 1
  uint16_t ch1_unreadconv : 1; // 2
  uint16_t ch0_unreadconv : 1; // 3
  uint16_t not_used_01    : 2; // 4-5
  uint16_t drdy           : 1; // 6
  uint16_t not_used_02    : 2; // 7-8
  uint16_t err_alw        : 1; // 9
  uint16_t err_ahw        : 1; // 10
  uint16_t err_wd         : 1; // 11
  uint16_t not_used_03    : 2; // 12-13
  uint16_t err_chan       : 2; // 14-15
} fdc_status_reg_t;
int main() {
    printf("sizeof(fdc_status_reg_t)=%d\n", (int)sizeof(fdc_status_reg_t));
}

$ arm-none-eabi-gcc -specs=rdimon.specs ./1.c && arm-none-eabi-gdb -ex 'target sim' -ex 'load' -ex 'run' -ex quit -quiet ./a.out
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
Connected to the simulator.
Loading section .init, size 0x18 lma 0x8000
Loading section .text, size 0xbf8c lma 0x8018
Loading section .fini, size 0x18 lma 0x13fa4
Loading section .rodata, size 0x30c lma 0x13fc0
Loading section .ARM.exidx, size 0x8 lma 0x142cc
Loading section .eh_frame, size 0x4 lma 0x142d4
Loading section .init_array, size 0x8 lma 0x242d8
Loading section .fini_array, size 0x4 lma 0x242e0
Loading section .data, size 0xad4 lma 0x242e8
Start address 0x80e8
Transfer rate: 421280 bits in <1 sec.
Starting program: /tmp/a.out 
sizeof(fdc_status_reg_t)=2
[Inferior 1 (process 42000) exited normally]

此外,您可以添加__attribute__((__packet__))以确保。

Josh Kunz 的 GCC 和 Clang 中的位域打包我认为这是我发现 gcc 如何处理位域和不同类型的最佳解释。

您提到了位带(在标题中),但您需要参考编译器文档(armcc 示例)来确定它是否以及如何使用位带来实现位字段。

位带是访问单个位的有效方式,但如果从位带区域读取,多于一位的成员将需要多次读取,因此编译器将从常规内存区域读取一个,对于为了提高效率,将避免跨越字边界,因此第 7 位和第 8 位中的 2 位字段将强制出现间隙。 也就是说,位带与多于一位的字段无关。

这当然不是 STM32 特定问题,而是 ARM Cortex-M3/M4 问题。 位带未在 Cortex-M7 上实现 - 所以也不是所有的 STM32。

当不使用位带时,任何情况下都不会跨越字边界。 在这种情况下,使用更大的单位类型(例如uint32_t将避免这种情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM