在布尔中写入0或1以外的值是UB吗?如果是,他们如何比较?

时间:2019-06-26 15:31:08

标签: c++ memory boolean equality

请考虑以下程序。

对于最近的gcc,所有比较都是正确的,但只有值1与x86的Visual Studio命令行编译器v.19.16.27031.1比较。

我认为通常可以通过char指针写入POD。但是标准中是否有关于将有趣的值写入bool变量的措辞?如果允许,是否有关于比较行为的文字?

#include <iostream>
using namespace std;

void f()
{
   if(sizeof(bool) != 1)
   {
      cout << "sizeof(bool) != 1\n";
      return;
   }

  bool b;

  *(char *)&b = 1;
  if(b == true) { cout << (int) *(char *)&b  << " is true\n"; }

  *(char *)&b = 2;
  if(b == true) { cout << (int) *(char *)&b  << " is true\n"; }

  *(char *)&b = 3;
  if(b == true) { cout << (int) *(char *)&b  << " is true\n"; }
}

int main()
{
    f();
}

P.S。 gcc 8.3使用test指令有效地检查非零值,而gcc 9.1显式地将其与1进行比较,从而仅使该比较为真。也许这个godbolt link有用。

5 个答案:

答案 0 :(得分:3)

否。这不行。

在布尔中写入任意数据非常费力(请参见What is the strict aliasing rule?),类似于Does the C++ standard allow for an uninitialized bool to crash a program?

*(char *)&b = 2;

这种类型的punning hack调用UB。根据{{​​1}}的编译器实现及其允许的优化,您可能拥有demons flying off your nose

答案 1 :(得分:2)

考虑:

bool b;
b = char{2};     // 1
(char&)b = 2;    // 2
*(char*)&b = 2;  // 3

在这里,第2行和第3行的含义相同,但第1行的含义不同。在第1行中,由于分配给bool对象的值不为零,因此保证结果为true。但是,在第2行和第3行中,bool对象的对象表示被直接写入。

通过const类型的左值写入任何非char类型的对象确实是合法的,但是:

在C ++ 17中,该标准未指定bool对象的表示形式。 bool类型可能具有填充位,甚至可能大于char。因此,以这种方式直接写入bool值的任何尝试都可能产生无效的(或“陷阱”)对象表示,这意味着随后读取该值将产生不确定的行为。实现可以(但不是标准要求的)定义bool对象的表示形式。

在C ++ 20中,我的理解是,由于P1236R1,不再有陷阱表示,但是bool的表示仍未完全指定。 bool对象可能仍然大于char,因此,如果仅写入对象的第一个字节,则它仍可以包含不确定的值,并在访问时产生UB。如果bool是1个字节(可能),则结果不确定-它必须产生基础类型的一些有效值(很可能是char或其有符号或无符号的表亲),但仍未指定此类值到truefalse的映射。

答案 2 :(得分:1)

可以将truefalse以外的值分配给类型bool的变量。

在分配值之前,通过使用标准转换顺序bool / true将RHS转换为false

但是,您尝试执行的操作不正确。

*(char *)&b = 2;  // Not OK
*(char *)&b = 3;  // Not OK

即使使用该机制分配1和0也不行。

*(char *)&b = 1;  // Not OK
*(char *)&b = 0;  // Not OK

以下语句可以。

b = 2; // OK
b = 3; // OK

根据OP的评论进行更新。

来自the standard/basic.types#basic.fundamental-6

  

布尔类型的值为truefalse

该标准未强制将true表示为1和/或false表示为0。实现可以选择最适合其需求的表示形式。

关于bool类型的值的standard goes on to say this

  

以本国际标准描述为“未定义”的方式使用bool值,例如通过检查未初始化的自动对象的值,可能会导致它的行为就好像它们都不是truefalse

将值char(1)char(0)间接存储在其存储位置中并不能保证将值正确转换为true / false。由于这些值在实现中可能无法表示truefalse,因此访问这些值将导致未定义的行为。

答案 3 :(得分:1)

通过指向除bool之外的类型的指针将任何整数值写入bool是未定义的行为,因为它们可能与编译器对该类型的表示不匹配。是的,写0或1之外的东西绝对会破坏事情:编译器通常依赖于布尔true的确切内部表示。

但是bool b = 3很好,只需将b设置为true(将整数类型转换为bool的规则是,任何非零值都将变为{{1} },零变成true)。

答案 4 :(得分:0)

通常,最好为bool分配0或1以外的值:

  

7.3.14布尔转换   [转换布尔]   1算术,无作用域枚举,指针或指针成员类型的prvalue可以转换为bool类型的prvalue。零值,空指针值或空成员指针值将转换为false;其他任何值都将转换为true。

但是您的转换完全是另一个问题。

请小心,认为可以通过指向其他内容的指针来写类型。您可以获得非常令人惊讶的结果,并且允许优化器假设某些事情没有完成。我不知道它的所有规则,但是优化器并不总是遵循指向不同类型的指针的写入操作(在存在未定义行为的情况下,它可以做各种事情!)但是请注意,这样的代码:

bool f()
{
    bool a = true;
    bool b = true;
    *reinterpret_cast<char*>(&a) = 1;
    *reinterpret_cast<char*>(&b) = 2;
    return a == b;
}

实时:https://godbolt.org/z/hJnuSi

经过优化: g ++:-> true(但实际上是2) 叮当声:-> false

main() {
    std::cout << f() << "\n";  // g++ prints 2!!!
}

尽管f()返回布尔值,但g ++实际上在这里main中打印出2。可能没想到。