如何用8个bool值创建一个字节(反之亦然)?

xce*_*cel 20 c++ boolean bit-manipulation bit-packing

我有8个bool变量,我想将它们"合并"成一个字节.

有一个简单/首选的方法来做到这一点?

相反,如何将一个字节解码为8个独立的布尔值?

我认为这不是一个不合理的问题,但由于我无法通过谷歌找到相关文档,它可能是另一个"非你所有直觉都是错误的"案例.

rod*_*igo 20

困难的方式:

unsigned char ToByte(bool b[8])
{
    unsigned char c = 0;
    for (int i=0; i < 8; ++i)
        if (b[i])
            c |= 1 << i;
    return c;
}
Run Code Online (Sandbox Code Playgroud)

和:

void FromByte(unsigned char c, bool b[8])
{
    for (int i=0; i < 8; ++i)
        b[i] = (c & (1<<i)) != 0;
}
Run Code Online (Sandbox Code Playgroud)

或酷的方式:

struct Bits
{
    unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
};
union CBits
{
    Bits bits;
    unsigned char byte;
};
Run Code Online (Sandbox Code Playgroud)

然后,您可以分配给联盟的一个成员并从另一个成员中读取.但请注意,位的顺序Bits是实现定义的.

  • 通过工会打字是UB; 请删除或明确声明这是一个扩展,以及哪些编译器提供它. (6认同)
  • 这绝对是UB. (3认同)
  • @ibug我认为通过联合的类型惩罚是C++中未定义的行为,regehr似乎在这里这样说:https://blog.regehr.org/archives/959我不认为陷阱表示的问题是相关的.这是关于严格的别名规则.上面显示的"酷"方式至少会违反我公司的编码标准. (2认同)

Lal*_*and 11

你可能想要研究一下std::bitset.它允许您将布尔值紧凑地存储为位,以及您期望的所有运算符.

毫无疑问,当你可以抽象出来时,有点傻逼.


phu*_*clv 8

很酷的方式(使用乘法技术

inline uint8_t pack8bools(bool* a)
{
    uint64_t t;
    memcpy(&t, a, sizeof t);         //  strict-aliasing & alignment safe load
    return 0x8040201008040201ULL*t >> 56;
       // bit order: a[0]<<7 | a[1]<<6 | ... | a[7]<<0  on little-endian
       // for a[0] => LSB, use 0x0102040810204080ULL    on little-endian
}

void unpack8bools(uint8_t b, bool* a)
{
       // on little-endian,  a[0] = (b>>7) & 1  like printing order
    auto MAGIC = 0x8040201008040201ULL;  // for opposite order, byte-reverse this
    auto MASK  = 0x8080808080808080ULL;
    uint64_t t = ((MAGIC*b) & MASK) >> 7;
    memcpy(a, &t, sizeof t);    // store 8 bytes without UB
}
Run Code Online (Sandbox Code Playgroud)

假设 sizeof(bool) == 1

要可移植地执行 LSB <-> a[0](如pext/pdep下面的版本)而不是使用主机字节序的反面,请htole64(0x0102040810204080ULL)在两个版本中用作魔术倍增器。(htole64来自 BSD / GNU <endian.h>)。这会安排乘法器字节以匹配 bool 数组的小端顺序。 htobe64使用相同的常量给出另一个顺序,MSB-first,就像你用来打印基数为 2 的数字一样。

您可能希望确保 bool 数组是 8 字节对齐 ( alignas(8)) 以提高性能,并且编译器知道这一点。 memcpy对于任何对齐总是安全的,但在需要对齐的 ISA 上,如果编译器memcpy知道指针已充分对齐,则它只能作为单个加载或存储指令内联。 *(uint64_t*)a将承诺对齐,但也违反了严格的别名规则。即使在允许未对齐负载的 ISA 上,它们在自然对齐时也可以更快。但是编译器仍然可以内联 memcpy 而不会在编译时看到该保证。


它们是如何工作的

假设我们有 8 个布尔值b[0],它们b[7]的最低有效位分别命名为 ah,我们想将它们打包成一个字节。将这 8 个连续的bools 视为一个 64 位字并加载它们,我们将在小端机器中以相反的顺序获得这些位。现在我们将进行乘法运算(这里的点是零位)

  |  b7  ||  b6  ||  b4  ||  b4  ||  b3  ||  b2  ||  b1  ||  b0  |
  .......h.......g.......f.......e.......d.......c.......b.......a
× 1000000001000000001000000001000000001000000001000000001000000001
  ????????????????????????????????????????????????????????????????
  ?......h.?.....g..?....f...?...e....?..d.....?.c......?b.......a
  ?.....g..?....f...?...e....?..d.....?.c......?b.......a
  ?....f...?...e....?..d.....?.c......?b.......a
+ ?...e....?..d.....?.c......?b.......a
  ?..d.....?.c......?b.......a
  ?.c......?b.......a
  ?b.......a
  a       
  ????????????????????????????????????????????????????????????????
= abcdefghxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Run Code Online (Sandbox Code Playgroud)

添加了箭头,以便更容易看到幻数中设置位的位置。此时 8 个最低有效位已放入最高字节,我们只需要将其余位屏蔽掉

所以包装的神奇数字是0b1000000001000000001000000001000000001000000001000000001000000001or 0x8040201008040201。如果您使用的是大端机器,则需要使用0x0102040810204080以类似方式计算的幻数

对于解包,我们可以做一个类似的乘法

  |  b7  ||  b6  ||  b4  ||  b4  ||  b3  ||  b2  ||  b1  ||  b0  |
                                                          abcdefgh
× 1000000001000000001000000001000000001000000001000000001000000001
  ????????????????????????????????????????????????????????????????
= h0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh
& 1000000010000000100000001000000010000000100000001000000010000000
  ????????????????????????????????????????????????????????????????
= h0000000g0000000f0000000e0000000d0000000c0000000b0000000a0000000
Run Code Online (Sandbox Code Playgroud)

相乘后,我们在最重要的位置有了所需的位,因此我们需要屏蔽掉不相关的位并将剩余的位移到最不重要的位置。输出将是包含 a 到 h 的小端字节。


有效的方式

在与新的x86 CPU BMI2PEXTPDEP用于此目的的说明。pack8bools上面的函数可以替换为

_pext_u64(*((uint64_t*)a), 0x0101010101010101ULL);
Run Code Online (Sandbox Code Playgroud)

unpack8bools功能可以实现为

_pdep_u64(b, 0x0101010101010101ULL);
Run Code Online (Sandbox Code Playgroud)

(这映射 LSB -> LSB,就像一个0x0102040810204080ULL乘数常数,与0x8040201008040201ULL.x86相反的是小端:a[0] = (b>>0) & 1;在 memcpy 之后。)

不幸的是,这些指令在 AMD 上非常慢,因此您可能需要与上面的乘法方法进行比较,看看哪个更好


Jer*_*ner 5

#include <stdint.h>   // to get the uint8_t type

uint8_t GetByteFromBools(const bool eightBools[8])
{
   uint8_t ret = 0;
   for (int i=0; i<8; i++) if (eightBools[i] == true) ret |= (1<<i);
   return ret;
}

void DecodeByteIntoEightBools(uint8_t theByte, bool eightBools[8])
{
   for (int i=0; i<8; i++) eightBools[i] = ((theByte & (1<<i)) != 0);
}
Run Code Online (Sandbox Code Playgroud)

  • 在没有任何解释的情况下发布代码解决方案可能有助于OP,但不能为其他用户提供良好的价值.您应该考虑添加评论和/或解释您的所作所为. (7认同)
  • @JeremyFriesner对于位掩码的新手来说,您的代码不是很简单,也不是一丁点(双关语)。我支持我先前的评论。 (2认同)

归档时间:

查看次数:

18307 次

最近记录:

6 年,6 月 前