【问题标题】:Using nibbles (4 bits variables) in windows C/C++在 Windows C/C++ 中使用半字节(4 位变量)
【发布时间】:2010-10-26 04:50:40
【问题描述】:

我正在编写网络标头,许多协议使用 4 位字段。有没有一种方便的类型可以用来表示这些信息?

我发现的最小类型是 BYTE。然后我必须使用大量的二进制操作来仅引用该变量中的几个位。

【问题讨论】:

    标签: c++ c windows types


    【解决方案1】:

    由于内存是字节寻址的,你不能寻址任何小于单个字节的单元。但是,您可以构建要通过网络发送的struct,并像这样使用bit fields

    struct A {
       unsigned int nibble1 : 4;
       unsigned int nibble2 : 4;
    };
    

    【讨论】:

    • 请注意,将变量标记为“无符号”很重要,否则编译器会将其视为有符号,您将看到负数。
    • 请注意,使用位域并不一定那么有效。这在传统上是一个问题。可能是现代编译器有所改进。至少有一个不错的机会,掩蔽和转换会更快 - 尽管不一定更清晰。
    • 这个结构有一个'unsigned int'的大小,因为有填充,至少在linux上是这样。那么使用'unsigned char'不是更好吗?
    • @quinmars:我建议告诉编译器结构已打包,而不是将其声明为unsigned char。在 gcc 中,您可以通过在右大括号之后(分号之前)添加 attribute__((__packed)) 来做到这一点。在 MSVC 中,您将结构声明包含在 #pragma pack (push,1) 和 #pragma pack (pop) 指令中(为了兼容性,gcc 也支持这一点)
    • 不是编译器hack。它只是一个指令,告诉编译器不要添加不必要的填充内容。使用unsigned char 的坏处是您仍然无法确定特定编译器没有添加填充以将其与单词或双字边界对齐(虽然本示例是这种情况,但可能不适用于更复杂的情况) .
    【解决方案2】:

    扩展 Mehrdads 的答案,同时使用带字节的联合以避免一些看起来很邪恶的类型转换:

    union Nibbler {
         struct { 
            unsigned int first:4;
            unsigned int second:4;
         } nibbles;
         unsigned char byte_value;
    }
    

    【讨论】:

    • 您应该在结构中使用 unsigned char 代替。否则会发生填充,并且会浪费 3 个字节的内存。在这样的应用程序中,您应该始终尝试将结构类型与联合类型匹配以避免填充。只需添加此内容,以防用户没有注意到主要答案中的 cmets
    【解决方案3】:

    似乎每个人都喜欢为此使用structs 中的位域。就个人而言,我将我所有的数据包代码都包装在对象中,这样你就看不到胆量了。我发现在协议代码中使用位域的问题是它鼓励使用结构作为内存上的覆盖。您可以安全地执行此操作,但您必须非常小心,以确保正确处理字节序和打包问题。除非你真的有充分的理由(例如,你正在编写从内存映射 IO 区域接收以太网数据包的代码),否则使用覆盖在内存上的位域会产生极其脆弱的代码恕我直言。

    我发现编写一个Packet 类要容易得多,它实现了各种位宽的提取、插入和覆盖例程。然后,根据从偏移量中提取特定宽度的值到本机整数等方面来实现数据包处理代码。隐藏抽象背后的所有字节序和打包问题,直到分析证明开销太大而无法承受。

    这是我希望我多年前学到的经验之一……您可能认为代码的可移植性不是问题,字节序也不是问题。相信我,当你的编译器改变它的填充算法或者你切换到不同的编译器时,这会给你带来多少麻烦,这会让你相信覆盖对于网络数据包处理代码来说是一个非常糟糕的想法。 p>

    【讨论】:

    • 解决方案的真正问题是 C(大多数设备驱动程序的编写语言)没有类。您需要求助于处理转换问题的全局函数。您关于填充的观点是正确的,但有一个解决方案:始终明确指定填充字段并告诉编译器打包结构
    • @Mehrdad, D. Shawley 说要实现一个类,但我认为他的意思是创建一个更高级别的抽象来摆脱字节序/位旋转/low-level-byte-ordering/等问题。诀窍是确保您添加的抽象层不会导致可怕的性能损失(对于这个主题,我认为相对容易)。
    • @Mehrdad:这个问题被标记为 C/C++,所以这就是类想法的来源。如果我们用 C 语言编写,那么 Trevor 是正确的——使用 C 语言中的一种 OOP 来实现抽象。我建议将 Ethereal/Wireshark 代码视为字节缓冲区的一个非常好的实现。他们实现了一个 Testy Virtual Buffer (TVB),这与我在 C++ 中所做的非常相似。
    【解决方案4】:

    在结构中使用字段:

    struct Header
    {
        unsigned int lowestNibble : 4;
        unsigned int anotherNibble : 4;
        unsigned int : 18;                 # Unnamed padding.
        bool aBool : 1;
        bool anotherBool : 1;
        unsigned int highestNibble : 4;
    };
    

    : 4 表示条目应占用 4 位。您可以使用任意数量的位。你可以使用任何你喜欢的内置类型。

    通常,您最终会将指向数据的指针转换为 Header *,然后执行以下操作:

    pHeader->lowestNibble = 5;
    

    【讨论】:

    • 我不确定这是否真的安全。我刚刚在 VS8 下编译了这个,sizeof(Header) 是 12 个字节。如果您将整个事物设置为零,然后单独将这些位设置为“所有位打开”,您会看到您的两个布尔字段占据了第五个字节的低位 - 它们从第 38 位而不是第 26 位开始可能会导致预期。基本上,“unsigned int”部分将占用完整的 sizeof(unsigned int)*_CHAR_BIT 位。
    【解决方案5】:

    不,没有方便的半字节类型。但是,使用宏或模板函数很容易制作它们。如果/当您需要处理字节序时,这尤其适用。

    食客

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 2023-02-24
      • 2018-11-29
      • 1970-01-01
      相关资源
      最近更新 更多