28

我有 8bool个变量,我想将它们“合并”成一个字节。

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

反过来,将一个字节解码为 8 个独立的布尔值怎么样?

我进来假设这不是一个不合理的问题,但由于我无法通过 Google 找到相关文档,这可能是另一种“你的直觉都是错误的”案例。

4

8 回答 8

27

艰难的道路:

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;
}

和:

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

或者很酷的方式:

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;
};

然后您可以分配给工会的一个成员并从另一个成员那里读取。但请注意,位的顺序Bits是实现定义的。

请注意,在编写另一个联合成员之后读取一个联合成员在 ISO C99 中是明确定义的,并且作为几个主要 C++ 实现(包括 MSVC 和 GNU 兼容的 C++ 编译器)的扩展,但在 ISO C++ 中是未定义的行为。 memcpyC++20std::bit_cast是在可移植 C++ 中键入双关语的安全方法。

(此外,a 中位域的位顺序char实现定义的,位域成员之间可能有填充。)

于 2011-12-11T01:01:28.000 回答
12

你可能想调查一下std::bitset。它允许您使用您期望的所有运算符将布尔值紧凑地存储为位。

当你可以抽象出来时,没有必要用比特翻转和诸如此类的东西鬼混。

于 2011-12-11T01:00:30.817 回答
10

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

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
}

假设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 个连续bool的 s 视为一个 64 位字并加载它们,我们将在 little-endian 机器中以相反的顺序获得这些位。现在我们将做一个乘法(这里的点是零位)

  |  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

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

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

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

  |  b7  ||  b6  ||  b4  ||  b4  ||  b3  ||  b2  ||  b1  ||  b0  |
                                                          abcdefgh
× 1000000001000000001000000001000000001000000001000000001000000001
  ────────────────────────────────────────────────────────────────
= h0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh
& 1000000010000000100000001000000010000000100000001000000010000000
  ────────────────────────────────────────────────────────────────
= h0000000g0000000f0000000e0000000d0000000c0000000b0000000a0000000

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


高效的方法

在具有BMI2的较新的 x86 CPU 上,有用于此目的的PEXTPDEP指令。上面的pack8bools函数可以替换为

_pext_u64(*((uint64_t*)a), 0x0101010101010101ULL);

unpack8bools功能可以实现为

_pdep_u64(b, 0x0101010101010101ULL);

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

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

于 2018-08-08T15:53:15.787 回答
6
#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);
}
于 2011-12-11T01:00:08.193 回答
2
bool a,b,c,d,e,f,g,h;
//do stuff
char y= a<<7 | b<<6 | c<<5 | d<<4 | e <<3 | f<<2 | g<<1 | h;//merge

尽管您可能最好使用 bitset

http://www.cplusplus.com/reference/stl/bitset/bitset/

于 2011-12-11T01:00:34.777 回答
2

我想指出,通过unions 进行双关的类型是 C++ 中的 UB(正如rodrigo他的回答中所做的那样。最安全的方法是memcpy()

struct Bits
{
    unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
};

unsigned char toByte(Bits b){
    unsigned char ret;
    memcpy(&ret, &b, 1);
    return ret;
}

正如其他人所说,编译器足够聪明,可以优化memcpy().

顺便说一句,这就是 Boost 键入双关语的方式。

于 2018-01-01T01:51:46.543 回答
0

您将使用按位移位操作和强制转换来存档它。一个函数可以像这样工作:

unsigned char toByte(bool *bools)
{
    unsigned char byte = \0;
    for(int i = 0; i < 8; ++i) byte |= ((unsigned char) bools[i]) << i;
    return byte;
}

感谢Christian Rau更正!

于 2011-12-11T00:58:55.563 回答
0

没有办法将 8bool个变量打包成一个字节。有一种方法使用Bitmasking将 8 个逻辑真/假状态打包在一个字节中。

于 2011-12-11T01:00:40.057 回答