【问题标题】:How to read/write arbitrary bits in C/C++如何在 C/C++ 中读/写任意位
【发布时间】:2012-08-02 16:37:47
【问题描述】:

假设我有一个二进制值为 11111111 的字节 b

例如,我如何读取从第二位开始的 3 位整数值或从第五位开始写入四位整数值?

【问题讨论】:

  • 您必须使用位操作,例如 &、>、|
  • 这个问题的更一般的答案,虽然针对的是非新手(借用你的描述性词):获取这本书 Hacker's Delight。那本书中的大多数食谱,一个普通人永远不需要实施,但如果你需要的是一本用于小玩意的食谱,它可能是这方面最好的书。
  • @BrianVandenberg - 这个问题的想法是了解位访问本质上是如何工作的,而不是一些会让人们摸不着头脑的超级 leet haxor 技巧。加上去年 SO 改变了对书籍建议等的政策。
  • 您的回复最初让我想走开,但我仍然不得不尝试帮助您。 “掌握正则表达式”被广泛认为是该主题的最佳参考书,Hacker's Delight 是 /learning/ 如何进行位操作的最佳参考书。书中解释了算法并给出了证明(或它们的草图)。如果让读者对算法摸不着头脑,那将更多地是因为他们缺乏经验而不是书。

标签: c++ c memory bit read-write


【解决方案1】:
int x = 0xFF;   //your number - 11111111

例如,我如何读取从第二位开始的 3 位整数值

int y = x & ( 0x7 << 2 ) // 0x7 is 111
                         // and you shift it 2 to the left

【讨论】:

  • 您还需要右移 2 以获得 0-7 之间的数字。此外,只需使用0x1c 即可简化掩码
【解决方案2】:

“例如,我如何读取从第二位开始的 3 位整数值?”

int number = // whatever;
uint8_t val; // uint8_t is the smallest data type capable of holding 3 bits
val = (number & (1 << 2 | 1 << 3 | 1 << 4)) >> 2;

(我假设“第二位”是第 2 位,即实际上是第三位。)

【讨论】:

  • 使用0x7 更容易,因为它与0b111 相同,与(1 &lt;&lt; 2 | 1 &lt;&lt; 3 | 1 &lt;&lt; 4) 相同。还有你转移到第三位,而不是第二位。
  • @Geoffrey 看到关于位编号的最后一句话。此外,任何体面的编译器都会优化冗长的 shift-and-or 部分,至少你可以第一眼看到你正在/正在做什么。
  • 如果你想让它更简单,只需使用 0b 语法,那个移位逻辑,而将被编译出来是一场噩梦,例如(number &gt;&gt; 2) &amp; 0b111
  • @Geoffrey 0b 语法是什么?这不是标准的 C。
  • 我可能会将它与另一种语言混淆,或者 GCC 接受它,但是是你的权利,不是标准 C。
【解决方案3】:

您需要对值进行移位和屏蔽,例如...

如果你想读取前两位,你只需要像这样屏蔽掉它们:

int value = input & 0x3;

如果你想抵消它,你需要右移 N 位,然后屏蔽掉你想要的位:

int value = (intput >> 1) & 0x3;

阅读您在问题中提出的三个位。

int value = (input >> 1) & 0x7;

【讨论】:

    【解决方案4】:

    读取字节使用 std::bitset

    const int bits_in_byte = 8;
    
    char myChar = 's';
    cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
    

    要编写,您需要使用按位运算符,例如 & ^ | & >。确保了解他们的工作。

    例如,要获得 00100100,您需要将第一位设置为 1,并使用 > 运算符将其移位 5 次。如果你想继续写,你只需继续设置第一位并移动它。这很像一台旧打字机:你写,然后换纸。

    对于00100100:将第一位设置为1,移位5次,将第一位设置为1,并移位2次:

    const int bits_in_byte = 8;
    
    char myChar = 0;
    myChar = myChar | (0x1 << 5 | 0x1 << 2);
    cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
    

    【讨论】:

      【解决方案5】:

      您必须执行移位和掩码 (AND) 操作。 令 b 为任意字节,p 为您要从中获取 n 位 (>= 1).

      首先你必须向右移动 b p 次:

      x = b >> p;
      

      其次,你必须用 n 个来掩盖结果:

      mask = (1 << n) - 1;
      y = x & mask;
      

      你可以把所有东西都放在一个宏里:

      #define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 << (n)) - 1)
      

      【讨论】:

        【解决方案6】:

        在我提出这个问题大约 2 年多之后,我想以我希望它解释的方式解释它,当时我还是一个完整的新手,并且对想要了解这个过程的人最有益。

        首先,忘记“11111111”示例值,它并不完全适合可视化的过程解释。所以让初始值为10111011(十进制的187),这将更能说明这个过程。

        1 - 如何读取从第二位开始的 3 位值:

            ___  <- those 3 bits
        10111011 
        

        值为101,即十进制的5,有两种可能的获取方式:

        • 掩码和移位

        在这种方法中,需要的位首先用值00001110(十进制的 14)进行掩码,然后移位到位:

            ___
        10111011 AND
        00001110 =
        00001010 >> 1 =
             ___
        00000101
        

        表达式为:(value &amp; 14) &gt;&gt; 1

        • 移位和遮罩

        这种方式类似,只是操作顺序颠倒了,即先将原值移位,然后用00000111(7)屏蔽,只留下最后3位:

            ___
        10111011 >> 1
             ___
        01011101 AND
        00000111
        00000101
        

        表达式为:(value &gt;&gt; 1) &amp; 7

        两种方法都涉及相同数量的复杂性,因此在性能上不会有差异。

        2 - 如何从第二位开始写入一个 3 位值:

        在这种情况下,初始值是已知的,当代码中出现这种情况时,您可以想出一种方法将已知值设置为另一个使用较少操作的已知值,但实际上这很少出现这种情况,大多数时候代码既不知道初始值,也不知道要写入的值。

        这意味着为了将新值成功“拼接”成字节,目标位必须设置为零,然后将移位后的值“拼接”到位,这是第一步:

            ___ 
        10111011 AND
        11110001 (241) =
        10110001 (masked original value)
        

        第二步是将我们要写入的值移到 3 位中,假设我们要将其从 101 (5) 更改为 110 (6)

             ___
        00000110 << 1 =
            ___
        00001100 (shifted "splice" value)
        

        第三步也是最后一步是将被屏蔽的原始值与移位的“拼接”值拼接起来:

        10110001 OR
        00001100 =
            ___
        10111101
        

        整个过程的表达式为:(value &amp; 241) | (6 &lt;&lt; 1)

        奖励 - 如何生成读写掩码:

        当然,使用二进制到十进制转换器远非优雅,尤其是在 32 位和 64 位容器的情况下 - 十进制值变得非常大。可以轻松地生成带有表达式的掩码,编译器可以在编译期间有效地解析:

        • “掩码和移位”的读取掩码:((1 &lt;&lt; fieldLength) - 1) &lt;&lt; (fieldIndex - 1),假设第一位的索引为 1(非零)
        • “移位和掩码”的读取掩码:(1 &lt;&lt; fieldLength) - 1(索引在这里不起作用,因为它总是移位到第一位
        • 编写掩码:只需使用~ 运算符反转“掩码和移位”掩码表达式

        它是如何工作的(上面例子中的 3 位字段从第二位开始)?

        00000001 << 3
        00001000  - 1
        00000111 << 1
        00001110  ~ (read mask)
        11110001    (write mask)
        

        同样的例子适用于更宽的整数和字段的任意位宽和位置,移位和掩码值会相应变化。

        还请注意,示例假定无符号整数,这是您想要使用的,以便将整数用作可移植位域的替代方案(标准绝不保证常规位域是可移植的),两者都离开并且右移插入一个填充0,而右移一个有符号整数则不是这种情况。

        更简单:

        使用这组宏(但仅限于 C++,因为它依赖于成员函数的生成):

        #define GETMASK(index, size) ((((size_t)1 << (size)) - 1) << (index))
        #define READFROM(data, index, size) (((data) & GETMASK((index), (size))) >> (index))
        #define WRITETO(data, index, size, value) ((data) = (((data) & (~GETMASK((index), (size)))) | (((value) << (index)) & (GETMASK((index), (size))))))
        #define FIELD(data, name, index, size) \
          inline decltype(data) name() const { return READFROM(data, index, size); } \
          inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); }
        

        你可以做一些简单的事情:

        struct A {
          uint bitData;
          FIELD(bitData, one, 0, 1)
          FIELD(bitData, two, 1, 2)
        };
        

        并将位字段实现为您可以轻松访问的属性:

        A a;
        a.set_two(3);
        cout << a.two();
        

        decltype 替换为 gcc 的 typeof pre-C++11。

        【讨论】:

        • 要将最终样本翻译成 C 语言还有一些工作要做。您需要 typedef struct A A; 才能定义 a 才能工作。同样在 C 中,您不能在结构范围内定义函数,这意味着需要进行一些重大更改(您需要将结构传递给函数等 - 符号更改是不可忽略的)。
        • 你是对的。我并没有严格关注 C,因为最初的问题也被标记为 C++。它仍然可以在 C 中应用,但使用“假”成员函数,即手动传递显式 this(或更好的是 self 以实现 C++ 编译器兼容性)指针。
        • 你在哪里定义value?它是一个字符数组吗?谢谢!
        • @tommy.carstensen - 我不确定我是否理解您的问题,该值只是一个无符号整数,为简洁起见,表示为单个字节。
        【解决方案7】:

        如果您不断从数据中获取位,您可能需要使用位域。您只需要设置一个结构并仅使用 1 和 0 加载它:

        struct bitfield{
            unsigned int bit : 1
        }
        struct bitfield *bitstream;
        

        然后像这样加载它(用 int 或您正在加载的任何数据替换 char):

        long int i;
        int j, k;
        unsigned char c, d;
        
        bitstream=malloc(sizeof(struct bitfield)*charstreamlength*sizeof(char));
        for (i=0; i<charstreamlength; i++){
            c=charstream[i];
            for(j=0; j < sizeof(char)*8; j++){
                d=c;
                d=d>>(sizeof(char)*8-j-1);
                d=d<<(sizeof(char)*8-1);
                k=d;
                if(k==0){
                    bitstream[sizeof(char)*8*i + j].bit=0;
                }else{
                    bitstream[sizeof(char)*8*i + j].bit=1;
                }
            }
        }
        

        然后访问元素:

        bitstream[bitpointer].bit=...
        

        ...=bitstream[bitpointer].bit
        

        所有这些都假设是在 i86/64 上工作,而不是 arm,因为 arm 可以是大端或小端。

        【讨论】:

        • 我不喜欢位域的原因是标准没有指定实现。不能保证布局在不同平台上是相同的。手动执行此操作可确保并允许快速有效的批量二进制序列化/反序列化。
        【解决方案8】:

        随意使用它:

        #define BitVal(data,y) ( (data>>y) & 1)      /** Return Data.Y value   **/
        #define SetBit(data,y)    data |= (1 << y)    /** Set Data.Y   to 1    **/
        #define ClearBit(data,y)  data &= ~(1 << y)   /** Clear Data.Y to 0    **/
        #define TogleBit(data,y)     (data ^=BitVal(y))     /** Togle Data.Y  value  **/
        #define Togle(data)   (data =~data )         /** Togle Data value     **/
        

        例如:

        uint8_t number = 0x05; //0b00000101
        uint8_t bit_2 = BitVal(number,2); // bit_2 = 1
        uint8_t bit_1 = BitVal(number,1); // bit_1 = 0
        
        SetBit(number,1); // number =  0x07 => 0b00000111
        ClearBit(number,2); // number =0x03 => 0b0000011
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-16
          • 2023-03-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多