【问题标题】:Casting struct to int in c++在 C++ 中将 struct 转换为 int
【发布时间】:2016-08-16 03:19:21
【问题描述】:

我有一个结构来表示具有如下位字段的 29 位 CAN 标识符。

struct canId
{
    u8 priority         :3; 
    u8 reserved         :1; 
    u8 dataPage         :1;     
    u8 pduFormat        :8;     
    u8 pduSpecific      :8;     
    u8 sourceAddress    :8;     
} iD;

在我的代码中,我想将此结构复制到一个整数变量中。比如:

int newId = iD; 

但是我不确定这是否正确。有人可以评论吗?

编辑:我可以在每个字段上使用移位运算符,然后使用按位 OR 将它们放在正确的位置。但这首先使使用带有位字段的结构毫无用处。

【问题讨论】:

  • 不,一点也不,除此之外,任何填充都足以把事情搞砸。
  • 联合可能是一个很好的解决方案。
  • @Siguza 这是位域,所以你必须看冒号的右边。
  • 我很确定这实际上并不那么容易。
  • @Boiethios 禁止读取与写入对象不同的联合字段,因此,不,这不是一个好的解决方案。

标签: c++ can-bus


【解决方案1】:

对于真正可移植的解决方案,您根本不应该使用位域结构,因为域布局是未定义的。相反,请使用整数和显式按位算术。

但对于更轻松的解决方案,int 与位域结构的联合就足够了。

为了兼顾安全性和便利性,您还可以考虑集成一个测试功能,该功能一次切换一个字段并检查与所需整数的匹配。


union
{
    uint32_t packed;

    struct canId
    {
        u8 priority         :3; 
        u8 reserved         :1; 
        u8 dataPage         :1;     
        u8 pduFormat        :8;     
        u8 pduSpecific      :8;     
        u8 sourceAddress    :8;     
    } split;
} iD;

iD.packed= 0; iD.split.priority= 7; if (iD.packed != 0x7) ...

【讨论】:

    【解决方案2】:

    如果只是C++,那么可以使用reinterpret_cast:

    struct canId {
        u8 priority         :3; 
        u8 reserved         :1; 
        u8 dataPage         :1;     
        u8 pduFormat        :8;     
        u8 pduSpecific      :8;     
        u8 sourceAddress    :8;     
    
        int &raw() {
            return *reinterpret_cast<int *>(this);
        }
    } iD;
    

    然后:

    int data = iD.raw();
    

    【讨论】:

      【解决方案3】:

      我能想象的最安全的方法是手动操作:

      int canIdToInt(canId id) {
          int temp = 0;
          int offset = 0;
      
          temp |= id.sourceAddress  << offset; offset += 8;
          temp |= id.pduSpecific    << offset; offset += 8;
          temp |= id.pduFormat      << offset; offset += 8;
          temp |= id.dataPage       << offset; offset += 1;
          temp |= id.reserved       << offset; offset += 1;
          temp |= id.priority       << offset; // offset += 3; redundant
      
          return temp;
      }
      

      当然,您可以将整个偏移量隐藏在宏后面以使其更清晰。

      #define START_WRITE int temp=0,offset=0
      #define RESULT temp
      
      #define ADD_VALUE(X) temp |= X << offset; offset += sizeof(X)
      

      实际上,sizeof 在此处的行为不会像预期的那样,但 another macro 会如此。

      【讨论】:

      • 唯一明智的解决方案。
      • 在你说“宏”之前,我对这个答案没有意见。
      • @RobK 因为它必须是有状态的,所以替代方案是 IntBuilder 类。嗯,其实……
      【解决方案4】:

      如果在 OP 中选择的这种格式的实现还没有固定下来,也许这会有所帮助。

      您可以按照 user3528438 的建议创建一个包装类来管理相关大小的单词并为调用者返回/设置相关部分。

      或者你可以做我做的事情(尽管不是针对 CAN)并创建 多个 包装器:对于每个“子字段”,创建一个 struct 持有一个 uint_or_whatever_t 以及成员函数/@987654323 @s 提取相关位。然后union多个这样的structs在一起。

      等等!收起你的类型双关干草叉!后一种模式是 例外,标准特别允许在标准布局structs 的公共初始序列(查找)中的成员交错读取union - 确保这些肯定访问相同的内存/未优化。

      确定我们处于定义行为领域后,真正的好处是如何巧妙地避免“本机”位域的实现定义布局:,而不是您的编译器,定义相关位的方式获取/设置。当然,您可以在 C 中手动执行此操作,使用内联 bitmanip 或辅助函数(我曾经这样做过),但通过使用类,所有的麻烦都隐藏在类实现中,这是应该的。

      例如

      class CanPriority {
          // the raw memory
          uint32_t m_raw;
      
      public:
          // getter/setter methods or operators
          uint8_t get() const { /* extract, shift... */ }
          // etc.
      };
      
      class CanReserved {
          uint32_t m_raw;
      
      public:
          uint8_t get() const { /* ... */ }
          // ...
      };
      
      union CanId {
          CanPriority priority;
          CanReserved reserved;
          // etc.
      };
      

      fwiw,对于其他情况,我已经用这种模式的一些非常精细的版本做了一些非常疯狂的事情 - 例如,想象一下当添加 templates 时你可以做什么 - 并且只有完美的结果,即使在-O3 -flto.

      【讨论】:

      • 你真的会写代码吗?听起来您在描述一个union 持有两个uint32_ts。
      • 我认为这根本没有帮助。 union 不能用来转换类型,就是这样。
      • @BartekBanachewicz 在访问属于共同初始序列的成员时当然可以。
      • 虽然这似乎符合标准,但我不确定它实际上如何帮助从原始结构转换。
      • @BartekBanachewicz 也许 OP 中的结构并不是一成不变的,在这种情况下,这可能是一种更好的方法,而 OP 只是没有想到。探索替代方案通常很好。
      【解决方案5】:
      struct Id
      {
          std::uint32_t bytes;
      
          // only considers least three significant bytes of x
          void priority(std::uint8_t x)
          { 
              bytes |= x & 0x03;
          }
      
          // only considers the least signifficant byte of x
          // sets byte into 4th bit of target value
          void reserved(std::uint8_t x)
          {
              bytes |= ((x & 0x01) << 4);
          }
      
          // ...
      };
      

      客户端代码:

      Id id;
      id.priority(0x1);
      id.reserved(0x0); // reset the "reserved" bit to zero
      

      尤其是这样做很危险:

      u8 priority         :3; 
      u8 reserved         :1; 
      

      您指定的这些位的表示是编译器特定的(而不是标准的一部分)。

      【讨论】:

        【解决方案6】:

        抛开位域的顺序是实现定义的问题,您总是可以填充位域以使其成为 32 位,然后将 memcpy 填充到您的 int 中:

        struct canId
        {
            u8 priority         :3; 
            u8 reserved         :1; 
            u8 dataPage         :1;     
            u8 pduFormat        :8;     
            u8 pduSpecific      :8;     
            u8 sourceAddress    :8;     
            u8 _padding         :3;
        } iD;
        
        int newId = 0;
        static_assert(sizeof(iD) <= sizeof(newId), "!");
        memcpy(&newId, &iD, sizeof(iD));
        

        这是完美定义的行为。不是通过union 键入双关语。

        【讨论】:

        • 我不相信这比 32 位联合更好?
        • @MartinJames 你的意思是unioncanIduint32_t?好吧,那将是未定义的行为,而事实并非如此。
        • @Barry 出于类似的原因,我拒绝了我的回答,因为我不确定“通用初始序列”允许中是否包含基本类型,它只明确提到了 structs
        • @Lightness 嗯? memcpy 是复制 pod 数据块的方式。您是否希望我使用 std::copyreinterpet_cast 来更加 C++y?
        • @Barry:是的。 memcpy 是您传播这种没有根据的观念的方式,即在类似但不完全相同的情况下编写 memcpy 是一个好主意,而这种类型安全的消除可能对代码维护造成毁灭性的影响。我发现一个 memcpy 曾经一直在吹嘘 std::map,因为有人更改了一个对象的类型并且没有为该对象所在的所有位置进行 grep memcpy (因为谁会呢?)。最佳实践 = 避免!
        【解决方案7】:

        我猜你只想从内存中取出这 29 位,然后在一次强制转换操作中将其打包到 int 中。 恐怕您无法确定您的结构是否包含在 29 位中,因为参考资料说:

        多个相邻的位域通常打包在一起(尽管这种行为是实现定义的)

        http://en.cppreference.com/w/cpp/language/bit_field

        所以你需要使用fritzone解决方案并编写类似的代码将其转换回来。

        另一种可能的解决方案是最初将 struct 包装成整数,然后包装 getter 和 setter 以选择正确的位。

        【讨论】:

          【解决方案8】:

          我希望我正确地做了一点魔术......你可能会做这样的事情:

          newID =  (int)iD.priority 
             | (int)(iD.reserved) << 3
             | (int)(iD.dataPage) << (3 + 1)
             | (int)(iD.pduFormat) << (3 + 1 + 1)
             | (int)(iD.pduSpecific) << (3 + 1 + 1 + 8)
             | (int)(iD.sourceAddress) << (3 + 1 + 1 + 8 + 8)
          

          但是为此,您需要一个int 至少有 32 位的系统

          【讨论】:

          • 这当然是一个选项,但它首先使位域结构的使用毫无用处。我编辑了问题。
          • @Paindoo:它如何使位域的使用“一开始就没有用”?可以肯定的是,切换到使用 int 是使它无用的原因,而不是这个解决方案。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-08-26
          • 2011-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-11-10
          相关资源
          最近更新 更多