【问题标题】:How to create a byte out of 8 bool values (and vice versa)?如何从 8 个布尔值中创建一个字节(反之亦然)?
【发布时间】:2012-01-17 16:02:30
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我不确定您的确切意思。 c++ 中的 bool(ean) 数据类型是一个字节,你想如何将一个字节转换为字节?
  • 没有办法将 8 个 bool 变量打包成一个字节。有一种方法使用位掩码将 8 个逻辑真/假状态打包在一个字节中。 en.wikipedia.org/wiki/Bitwise_operation
  • @ScarletAmaranth 这应该是一个答案。
  • @weltraumpirat 我不确定问题到底是什么。
  • 我只知道人们会让这个问题变得更难。好吧,我不知道布尔值的大小超过 1 位是我的错。

标签: c++ boolean bit-manipulation bit-packing


【解决方案1】:

艰难的道路:

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++20 std::bit_cast 是在可移植 C++ 中键入双关语的安全方法。

(另外,char 中位域的位顺序是implementation defined,位域成员之间可能有填充。)

【讨论】:

  • @Juicy 不直接使用联合,如果需要循环使用&lt;&lt;。但是你可以混合使用这两种方法。
  • @ibug 我认为通过联合进行类型双关是 C++ 中未定义的行为,regehr 似乎在这里这么说:blog.regehr.org/archives/959 我认为陷阱表示的问题不相关。这是关于严格的别名规则。上面显示的“酷”方式至少会违反我公司的编码标准。
  • @iBug 访问未最后分配的工会成员是 UB。
  • 通过联合的双关语类型是 UB;请删除它或明确声明这是一个扩展以及哪些编译器提供它。
  • 这绝对是 UB。
【解决方案2】:

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

当你可以抽象出来的时候,没有必要用比特翻转之类的玩弄。

【讨论】:

【解决方案3】:

很酷的方式(使用multiplication technique

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 is from BSD / GNU <endian.h>)。这会安排乘数字节以匹配 bool 数组的 little-endian 顺序。 htobe64 具有相同的常数给出了另一个顺序,MSB 优先,就像您用于打印以 2 为底的数字一样。

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


它们是如何工作的

假设我们有 8 个布尔值 b[0]b[7],它们的最低有效位分别命名为 a-h,我们希望将它们打包成一个字节。将这 8 个连续的 bools 视为一个 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 个最低有效位已放入最高字节,我们只需将其余位屏蔽掉

所以包装的幻数是0b10000000010000000010000000010000000010000000010000000010000000010x8040201008040201。如果您使用的是大端机器,则需要使用以类似方式计算的幻数 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 乘数常数,与 0x8040201008040201ULL 相反。x86 是 little-endian:a[0] = (b&gt;&gt;0) &amp; 1; 在 memcpy 之后。)

不幸的是those instructions are very slow on AMD所以你可能需要和上面的乘法比较看看哪个更好

【讨论】:

  • to_ullongunsigned long long 构造函数不能使用 PEXT 和 PDEP,因为它们只是将 unsigned long long 值直接复制到内部数组或从内部数组复制。 bitset 中的位存储为数组中的位,而不是每个字节一个位。所以to_ullong 给你 64 位而不是 8
  • @SamuelLi 这可能是由于strict aliasing。试试-fno-strict-aliasing 或工会。或者只是从bool 更改为char,因为char* 可以为其他类型起别名
  • @SamuelLi:那是因为使用uint64_t* 读取char[] 对象违反了严格别名。 ISO C 只允许它反过来:使用char * 读取最初是uint64_t 的对象。如果对 char 对象的唯一访问是通过 char*,那很好,但如果有任何使用自动或静态存储 char[],你就有了 UB。这个答案真的应该使用memcpy 或 C++20 std::bitcast 或 GNU C typedef uint64_t unaligned_aliasing_u64 __attribute__((aligned(1), may_alias)); 来做类型双关语;指针转换从来都不是安全的。
  • @SamuelLi:在 C++ 中,自动存储 = 非静态本地;静态存储 = 全局和静态任何东西。我的观点是,如果有某种方法可以访问数组对象本身,尤其是作为结构的一部分,那么严格别名可能会出现真正的问题。如果“数组”正是您处理从newmalloc 获得的一些内存的方式,那么对它的所有char 访问都将通过char* 进行,因此别名安全。但是,如果您在某处有一个真正的 char array[8] 变量声明,则可能无法通过 char* 访问它。
  • @SamuelLi:虽然我刚刚意识到即使使用动态存储也会有问题。如果您有struct foo{ int a; char b[8];};,那么您甚至可以通过指向它的指针对整个结构进行结构分配,例如foo *p=..., *q=...;*p = *q。在这种情况下,不是通过char* 访问char b[8],因此编译器当然可以假设某些uint64_t* 加载或存储与它无关。 (你可能实际上需要这个 struct 来让 UB 真正发生,或者至少在实践中成为一个问题,而不仅仅是 char arr[8] local var: [] 访问通过衰减到 char* 起作用)
【解决方案4】:
#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);
}

【讨论】:

  • 在没有任何解释的情况下发布代码解决方案可能会帮助 OP,但不会为其他用户提供良好的价值。你应该考虑添加 cmets 和/或解释你做了什么。
  • +1 用于使用 uint8_t。当您需要 8 位时,这正是该类型的用途。
  • 我希望您意识到eightBools[i]bool 并使用== true 检查它,您也可以只写(eightBools[i] == true) == true((eightBools[i] == true) == true) == true,但是在哪里停止呢?是的,这不值得为答案投票。
  • weltraumpirat 我认为代码足够简单,单独的解释是多余的,只会妨碍。 Christian 我接受你的批评——我不会在我自己的代码中这样写——但我在这里这样写是为了让代码的意图更清晰。你可以继续投票,我不想要它;)
  • @JeremyFriesner 对于位掩码新手来说,您的代码并不简单,一点也不(双关语)。我支持我之前的评论。
【解决方案5】:
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/

【讨论】:

  • 硬编码是不是太多了?
  • 视情况而定,我不会写,但如果你想合并 8 个单独的非连续布尔值,那么它就是这样做的方法。
【解决方案6】:

我想指出,通过unions 的类型双关语是 C++ 中的 UB(就像 rodrigohis answer 中所做的那样。最安全的方法是 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 键入双关语的方式。

【讨论】:

  • @Michi 这个问题是 C++ 吗?
  • 虽然我喜欢这个解决方案的简单性,但看起来位字段的内存布局取决于编译器link。这对于某些用例来说是不受欢迎的......
【解决方案7】:

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

【讨论】:

  • 您可以将8个bools的打包成一个字节。当然,它们不再是单独的 C++ bool 对象,但所有 8 个值都在一个 charuint8_t 的位内。当期望的行为很明确时,这个答案似乎没有帮助和迂腐。
  • 鉴于我的水晶球在回答时被打破,我无法通过读心来确定意图是什么。我回答了提出的问题。
【解决方案8】:

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

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的更正s

【讨论】:

  • 我(pst)不懂任何 C++ ...所以如果有人能澄清为什么这个问题被否决,非常感谢!
  • 投票的人真的可以告诉我我做错了什么吗?我对编程很天真。 ://
  • 您使用short(可能是1个字节,但很可能是2个字节)而不仅仅是char(保证是1个字节)的原因是...... .?而且你应该使用无符号类型并正确初始化byte。解决这些问题,答案更有可能是正确的。但我不是反对者,还不是。
  • 哦,是的,我现在正在编辑,感谢您指出。
  • unsigned char byte = \0; 无效。使用0'\0'
猜你喜欢
  • 2014-11-30
  • 2021-12-23
  • 1970-01-01
  • 2014-10-14
  • 2021-09-19
  • 1970-01-01
  • 2014-04-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多