【问题标题】:Order of fields when using a bit field in C在 C 中使用位域时的域顺序
【发布时间】:2013-10-22 23:23:00
【问题描述】:

我有以下类型的结构

typedef struct
{
unsigned int a : 8;
unsigned int b : 6;
unsigned int c : 2;
}x, *ptr;

我想做的是改变字段 c 的值。

我做了类似以下的事情

x structure = { 0 };
x->c = 1;

当我查看内存映射时,我希望找到 00 01,但我却找到了 00 40。 看起来在排列第二个字节时,它将 c 字段放在最低位,将 b 字段放在最高位。我在 GCC 和 Windows 编译器上都看到了这一点。

目前,我所做的是以下,一切正常。

unsigned char ptr2 = (unsigned char*) ptr
*(ptr2 + 1)  &= 0xFC
*(ptr2 + 1)  |= 0x01

我看错了内存映射吗? 感谢您的帮助。

【问题讨论】:

  • 如何显示 00 40 值?
  • 假设ptr 持有&structure(您的问题不清楚)*(ptr+1) 是快速进入未定义行为
  • @WhozCraig - 抱歉,这里的代码不完整,我已将转换为 unsigned short 以便每次移动 1。
  • @ouah - 我在 Visual Studio 中检查了内存映射,稍后打印了变量/字段的值。
  • @fashasha 好的。 *ptr 作为类型和 ptr 作为变量让我头疼。我想我明白你在做什么。 ptr 在取消引用代码中是(现在unsigned char*

标签: c gcc bit-manipulation bit bit-fields


【解决方案1】:

内存始终取决于底层机器结构(字节序)以及编译器正在执行的打包/排列结构的策略。

【讨论】:

    【解决方案2】:

    C 标准允许编译器以任何顺序放置位域。没有可靠且可移植的方式来确定顺序。

    如果您需要知道确切的位位置,最好使用普通的无符号变量和位掩码。

    这是使用位域的一种可能的替代方法:

    #include <stdio.h>
    
    #define MASK_A    0x00FF
    #define MASK_B    0x3F00
    #define MASK_C    0xC000
    #define SHIFT_A   0
    #define SHIFT_B   8
    #define SHIFT_C   14
    
    unsigned GetField(unsigned all, unsigned mask, unsigned shift)
    {
        return (all & mask) >> shift;
    }
    
    unsigned SetField(unsigned all, unsigned mask, unsigned shift, unsigned value)
    {
        return (all & ~mask) | ((value << shift) & mask);
    }
    
    unsigned GetA(unsigned all)
    {
        return GetField(all, MASK_A, SHIFT_A);
    }
    
    unsigned SetA(unsigned all, unsigned value)
    {
        return SetField(all, MASK_A, SHIFT_A, value);
    }
    
    /* Similar functions for B and C here */
    
    int main(void)
    {
        unsigned myABC = 0;
        myABC = SetA(myABC, 3);
        printf("%u", GetA(myABC)); // Prints 3
    }
    

    【讨论】:

    • C99 6.7.2.1-11:实现可以分配任何大到足以容纳位字段的可寻址存储单元。如果有足够的空间,结构中紧跟在另一个位域之后的位域将被打包到同一单元的相邻位中。如果剩余空间不足,则将不适合的位域放入下一个单元还是与相邻单元重叠是实现定义的。单元内位域的分配顺序(高位到低位或低位到高位)是实现定义的。未指定可寻址存储单元的对齐方式。
    • @WhozCraig - 是的,对不起,正如我在第一次回复你时提到的,我忘了把这个添加到帖子中。无论如何,感谢您从标准中指出这条线。我猜对 setting 字段使用位掩码就足够了。我想我仍然可以使用该结构来获取字段的值。
    • 位的顺序和字节序有关系吗?
    • @GinuJacob 一些关系。如果你这样做(unsigned)number &amp; 1,它总是会给你LSB,并且字节顺序无关紧要。 字节序: 如果您检查 number 的字节,则具有 LSB 的字节是实现定义的。在 little-endian 系统上,LSB 可以在第一个字节上找到。 Bit-endianness:在 C 中不可能知道单个字节中的位顺序。简而言之:如果您编写转换数据的代码,则需要关心字节 edianness到字节或返回。你真的不需要关心位字节序,因为 C 没有能力获取单个位的地址。
    • @user1582568 为了便于理解,我把这个例子留得很简单。在使用良好的编译器应用函数内联和优化之后,我希望性能非常接近或完全相同。毕竟,位域需要在后台执行相同的移位和屏蔽操作。
    【解决方案3】:

    我知道这是一个旧的,但我想补充一下我的想法。

    1. C 中的标头旨在跨对象使用,这意味着编译器必须保持一定的一致性。

    2. 根据我的经验,我总是看到 LSB 顺序的位域。这会将位按 c:b:a 的 MSB->LSB 顺序放置

    按字节顺序读取的 16 位是“00 40”。从 Little-endian 翻译过来,这是一个 16 位的值 0x4000。

    这是:

    c == [15:14] == b'01
    b == [13:8] == 0
    a == [7:0] == 0
    

    【讨论】:

      【解决方案4】:

      您将 C 结构设置为原始位,后果自负。

      您知道位是什么以及它们的含义,因此您可以填写结构的字段。是的,它比 memcpy 代码多,但如果有人添加一个字段,它不会中断,并且如果有助于在通信级别强制执行位级特异性。

      【讨论】:

        猜你喜欢
        • 2015-12-05
        • 2014-09-15
        • 2010-12-02
        • 2010-09-06
        • 2023-03-10
        • 1970-01-01
        • 1970-01-01
        • 2016-06-21
        相关资源
        最近更新 更多