C 如何将结构强制转换为uint8_t(错误:请求转换为非标量类型)

C 如何将结构强制转换为uint8_t(错误:请求转换为非标量类型),c,casting,embedded,bit-fields,C,Casting,Embedded,Bit Fields,我需要在EEPROM中存储8个继电器的状态。我不想为移位而烦恼,我喜欢使用位域。所以我想这样定义它们: typedef struct{ uint8_t RELAY0_STATE:1; uint8_t RELAY1_STATE:1; uint8_t RELAY2_STATE:1; uint8_t RELAY3_STATE:1; uint8_t RELAY4_STATE:1; uint8_t RELAY5_STATE:1; uint8_t RE

我需要在EEPROM中存储8个继电器的状态。我不想为移位而烦恼,我喜欢使用位域。所以我想这样定义它们:

typedef struct{
    uint8_t RELAY0_STATE:1;
    uint8_t RELAY1_STATE:1;
    uint8_t RELAY2_STATE:1;
    uint8_t RELAY3_STATE:1;
    uint8_t RELAY4_STATE:1;
    uint8_t RELAY5_STATE:1;
    uint8_t RELAY6_STATE:1;
    uint8_t RELAY7_STATE:1;
}relay_nvm_t;

relay_nvm_t   relay_nvm;
在我的主代码流中,我使用
relay\u nvm
变量设置每个继电器的状态。范例

...
if(something)
{
    relay_nvm.RELAY0_STATE = 1;
    relay_nvm.RELAY1_STATE = 0;
    relay_nvm.RELAY2_STATE = 1;
    relay_nvm.RELAY3_STATE = 0;
    relay_nvm.RELAY4_STATE = 1;
    relay_nvm.RELAY5_STATE = 1;
    relay_nvm.RELAY6_STATE = 0;
    relay_nvm.RELAY7_STATE = 1;
}
最后,当我需要读/写EEPROM时,我只是将
relay\u nvm
转换为
uint8\u t
来读/写一个字节到EEPROM。但是我得到了
错误:转换为请求的非标量类型
错误。以下是我的职能

static void NVM_Relay_Read(void)
{
    relay_nvm = (relay_nvm_t)NVM_ReadEepromByte(NVM_RELAY_INDEX);
}

static void NVM_Relay_Write(relay_nvm_t rs)
{
    NVM_WriteEepromByte(NVM_RELAY_INDEX, (uint8_t)rs);
}
我们有没有办法克服这个错误?我想我可以通过打字来完成。位字段的使用使我的工作非常简单,代码也很容易理解


我知道在这种情况下,由于填充,位字段可能不安全,但我认为我可以使用
POP-PUSH
(值得吗?

我看到了更多处理方法:

  • 使用联合体

  • 使用指针类型强制转换:
    *((uint8\u t*)和relay\u nvm)

  • 使用uint8\u t:

  • 我不想为移位而烦恼,我喜欢使用位域

    如果使用位运算符是一个“麻烦”,那么在掌握了它们之前,您可能不应该编写嵌入式系统代码。。。对于编写非标准、不可移植的代码来说,这是一个非常糟糕的理由

    与按位版本不同,位字段有很多问题:未定义的位顺序、endianess依赖性、指定的符号性差、对齐和填充打嗝等等。您已经在使用
    uint8\t
    位字段编写特定于平台的代码,因为这些字段不在C标准的范围内

    如果您坚持使用位字段,您必须阅读特定的编译器文档,了解它们是如何实现的。不要假设在如何分配方面有任何保证,因为在这种情况下没有标准化


    您的具体问题是,您无法直接从结构类型(聚合-在简单的英语中是“容器类型”)转换为
    uint8\t
    ,原因与无法使用数组进行转换相同。您必须使用指向第一个元素的指针,然后将该指针强制转换为
    uint8\t*
    并取消引用。但这也带来了与对齐、兼容类型和“严格别名”相关的一系列其他问题

    结构和联合通常不太适合内存映射,尤其是在可移植性方面。至少,您必须使用
    #pragma pack(1)
    或类似的编译器特定命令来启用打包

    <> P>因此,您应该考虑完全删除位字段,并在原始代码> UTI8GYT 中使用位运算符,因为这样解决了很多问题。

    作为旁注,EEPROM中存储的所有变量必须是
    易失性的
    限定变量,所有指向它们的指针也必须是限定变量。否则,当您启用优化时,程序很可能会失控。

    您可以键入一个指向变量的指针:
    *((uint8\u t*)&relay\u nvm)
    。或者使用一个联合体(联合体的一个分支将是uint8\u t,另一个将是您的结构类型)。使用联合体是一个非常好的主意!我怎么能不这样想:)请注意,位字段不是顺序定义的。因此,如果您试图在其他地方使用此代码,它将被破坏。答案中指出,最好的选择是只使用
    uint8\t
    作为寄存器,并将数据移入和移出位。这是最好的方法,也最不可能中断。位运算符是更好的方法,比位字段更安全、更便于携带。但是请记住,像
    1
    这样的十进制整数常量是
    int
    类型,因此如果在16位
    int
    系统上使用
    RELAY15_MASK 32768
    (可能是微控制器),那么您可能会遇到各种各样的细微符号错误。十六进制常量本可以解决这个问题,但更好的是总是
    u
    为所有整数常量添加后缀。谢谢你的回答。我知道使用
    位字段的问题,但在大多数情况下,使用它们而不是
    按位移位操作对我来说更清楚、更简单。我想如果我使用
    pack
    指令,它不会有什么坏处,因为它只有1个字节。你认为编译器仍然有机会在位之间添加填充吗?@abdullahcinar问题在于编译器并不是为了添加填充才添加填充:如果需要填充,CPU需要对齐。强制编译器打包可能意味着最终会导致访问未对齐。不会使事情变得更糟,(仿真)EEPROM通常有自己的各种对齐要求,也有一个段的最小擦除大小。感谢解释,我将考虑在这样的情况下不使用位字段。
        uint8_t relay_nvm;
        #define RELAY0_MASK 1
        #define RELAY1_MASK 2
        #define RELAY2_MASK 4
        ...
        #define RELAY7_MASK 128
    
        // set exact relays state:
        relay_nvm = RELAY0_MASK | RELAY2_MASK | RELAY4_MASK | ... ;
    
        // set single relay (others left unchanged):
        relay_nvm |= RELAY2_MASK;
    
        // clear single relay (others left unchanged):
        relay_nvm &= ~RELAY2_MASK;
    
        // check current state of a relay:
        if (relay_nvm & RELAY2_MASK) { ... }