当赋值时,布尔值是否应被截断为true或false?

3
我发现在一个bool变量中存储的值(顺便提一下,这是在Visual-C++和clang++之间的差异)存在差异,特别是当存储的值既不是true也不是false(如果它以某种方式被破坏),我不确定这是Visual-C++的错误还是我应该忽略的UB问题。
请看以下示例:
#include <cstdint>
#include <iostream>
#include <string>
#include <limits>
bool inLimits(bool const v)
{
    return (static_cast<std::int32_t>(v) >= static_cast<std::int32_t>(std::numeric_limits<bool>::min()) && static_cast<std::int32_t>(v) <= static_cast<std::int32_t>(std::numeric_limits<bool>::max()));
}
int main()
{
    bool b{ false };
    bool const* const pb = reinterpret_cast<bool const*>(&b);
    std::uint8_t * const pi = reinterpret_cast<std::uint8_t*>(&b);

    std::cout << "b: " << b << " pb: " << (*pb) << " pi: " << std::to_string(*pi) << std::endl;
    std::cout << "b is " << (inLimits(b) ? "" : "not ") << "in numeric limits for a bool" << std::endl;

    *pi = 3; // Simulate a bad cast during boolean creation
    bool const b2{ b };
    bool const b3{ *pb };

    std::cout << "b: " << b << " pb: " << (*pb) << " pi: " << std::to_string(*pi) << std::endl;
    std::cout << "b2: " << b2 << " b3: " << b3 << std::endl;

    std::cout << "b is " << (inLimits(b) ? "" : "not ") << "in numeric limits for a bool" << std::endl;
    std::cout << "b2 is " << (inLimits(b2) ? "" : "not ") << "in numeric limits for a bool" << std::endl;
    std::cout << "b3 is " << (inLimits(b3) ? "" : "not ") << "in numeric limits for a bool" << std::endl;

    return 0;
}

这是Visual-C++的输出结果。

b: 0 pb: 0 pi: 0
b is in numeric limits for a bool
b: 3 pb: 3 pi: 3
b2: 3 b3: 3
b is not in numeric limits for a bool
b2 is not in numeric limits for a bool
b3 is not in numeric limits for a bool

这是clang++的输出结果

b: 0 pb: 0 pi: 0
b is in numeric limits for a bool
b: 1 pb: 1 pi: 3
b2: 1 b3: 1
b is in numeric limits for a bool
b2 is in numeric limits for a bool
b3 is in numeric limits for a bool

似乎在使用clang++构造新的布尔值时,以及在流操作符中使用时,存在限制检查。
我应该忽略这个问题吗?或者这只是Visual-C++独有的bug?
谢谢!
编辑:对于那些没有理解示例目的的人,它只是一个展示,用来“模拟”代码其他部分中的内存损坏或错误,导致布尔值被初始化为true或false以外的其他值,无论布尔值的二进制表示如何。
(我想知道是否需要在其他地方保护我的代码免受不当使用,例如使用assert,但前提是这种行为不是UB)
第二次编辑:添加了numeric_limits代码。

reinterpret_cast 可能会在像这样的操作中产生未定义的行为。因此,也许这种行为是超出标准范围之外的? - jwimberley
同意。如果您想复制表示,使用memcpy - chris
对于那些不理解示例目的的人,它只是一个展示,用于“模拟”内存损坏或代码中其他部分中的错误,导致布尔值被初始化为除了true或false之外的其他值,无论bool的二进制表示如何。(我在想是否需要在其他地方使用assert来保护我的代码免受不当使用,但前提是这种行为不是UB) - Chris
类似的问题,http://stackoverflow.com/questions/28207856/changing-a-bool-to-a-value-other-than-0-or-1/28208832#28208832 - M.M
@M.M 我在问题中更新了我的numeric_limits库代码,但是忘记在这里包含它。正如你所看到的,在Visual案例中,赋值不会转换为true或false,这已经超出了numeric_limits的范围(根据规格说明,最小值为false,最大值为true)。 - Chris
显示剩余10条评论
4个回答

3

"在存储的值既不是true也不是false的情况下"

你认为这种情况是为什么呢?C ++不限制bool的二进制表示。在某些编译器上,true可以表示为00000011,而其他编译器则可以选择将false表示为00000011

但事实上,无论是GCC还是MSVC都不使用该位模式来表示任何bool值。 这确实使它成为未定义行为。 UB永远不能是编译器错误。 错误是指实现不按预期工作,但UB特别指任何实际行为都是可接受的。


C++本身不支持,但STL有一点支持,因为布尔值必须由std::numeric_limits<bool>::min和max组成。 - Chris
@Chris:我想你指的是“标准库”。这是C++的一个组成部分,所以你在那里不能有所区别。 - MSalters
当然,但我并没有假设任何关于bool的二进制表示的事情。我的观点只是确定这种行为是一个bug还是规范说明了它是UB。我的代码不能控制bool的创建方式,但我会检查它是否在numeric_limits的范围内,出于其他原因(主要是因为它是一个模板)。所以我想知道如果值由于某些原因无效(在我之前的代码中编码错误),我是否应该抛出错误,或者如果它是UB(在这种情况下,我只需断言)。很难从一个巨大的库代码中提取一个小样本来展示问题 :) - Chris
1
@Chris:使用另一种类型的表达式访问值仅适用于少数特殊情况。仅当3恰好是truefalse的二进制表示时,*pi=3才有效。请注意,您不能说false必须为0,这就是static_cast的工作方式。它转换值。reinterpret_cast转换位模式。 - MSalters

2

标准并没有规定 bool 的值表示是什么。编译器可以自由地进行规定。

你的证据表明,VC++ 要求将 true 表示为只有 LSB 设置,而 clang++ 允许任何非零表示为 true

对于 VC++,你的代码在 bool const b2{ b }; 这一行会导致未定义行为,特别是当它试图从 b 中读取值时。存储中设置的位不对应于 b 的值,并且标准没有定义在这种情况下会发生什么,因此它是未定义行为。

当发生未定义行为时,没有任何保证;程序的所有输出都是无意义的。你不能根据此后出现的输出语句(甚至包括之前的语句)推断任何东西。


这正是我的问题:“对于VC++,您的代码在bool const b2 {b}行引起未定义行为”,我正在寻找C++规范说明这是否是UB(未定义行为)的地方。 - Chris
@Chris,我认为这是一种未定义的省略:标准并没有说明当lvalue-to-rvalue转换遇到没有特定值定义的比特模式时会发生什么,因此它是未定义行为。类似的情况是,在普通系统上,如果您将float中的位设置为IEEE754未覆盖的内容。 - M.M
非常感谢您的澄清,正是我所需要的。 - Chris

1
由于我在C++标准中没有找到有关指向bool(或等效)的转换的信息(如果使用这些是定义的),因此我不愿将其发布为答案。但是,经过再次考虑,我可能会发布它-其他人可能会详细说明。
首先,C++14标准将bool定义为:
[basic.fundamental] 6. bool类型的值为true或false。[注:没有带符号、无符号、短或长的bool类型或值。—end note] bool类型的值参与整数提升(4.5)
由于它参与整数提升,因此为其定义了以下提升:
[conv.prom] 6. bool类型的prvalue可以转换为int类型的prvalue,false变成零,true变成一。
而且,由于您正在使用std::ostream::operator<<进行打印,因此对于bool,它的定义如下:

[ostream.inserters.arithmetic]

  1. 类 num_get<> 和 num_put<> 处理与区域设置相关的数字格式化和解析。

由于它使用 num_put<> 进行实际输出,因此与 bool 输出相关的代码片段定义如下:

[facet.num.put.virtuals]

  1. 如果 (str.flags() & ios_base::boolalpha) == 0,则返回 do_put(out, str, fill, (int)val)

由于您在示例中没有使用 boolalpha,因此应适用典型的整数提升规则(如上所述)。

此外,我仍然无法解释为什么在 *pi = 3std::to_string(*pi) 在两种情况下都打印出 3,但它可能与以下内容有关:

[expr.reinterpret.cast]

  1. [注:reinterpret_cast 执行的映射可能与原始值不同。—end note]

iostream并不是一个问题,它只是用来打印值的,我想知道为什么clang会纠正它。但我喜欢你提到关于bool的c++14参考(我没有找到),这让我觉得这可能是Visual Studio的一个bug,因为它应该使用true或false来构造,而不是其他东西! - Chris
@Chris 我知道,iostream 本身并不是问题,但我引用它来证明,最终 bool 在尝试打印时会被强制转换为 int,这应该会触发整数转换规则。 - Algirdas Preidžius
确切的说 :) 实际上我提问的目的是为了尝试找到关于“gets”(在某些编译器上)或“应该得到”(如果编译器没有则是一个错误)的答案。由于您的回答似乎最接近我所寻找的,我会将您的帖子标记为答案,再次感谢。 - Chris

0

不确定这是否有帮助,但是g++表现出与Visual-C ++相同的行为。

这是我得到的输出:

b:0 pb:0 pi:0
b:3 pb:3 pi:3
b2:3 b3:3

从我的理解来看(我不是C ++编译器专家),reinterpret_cast指示编译器将位集视为新类型。因此,当您要求编译器重新解释布尔值的地址作为8位整数时,它实质上也将原始布尔值转换为8位整数(如果有意义的话)。

因此,如果我的解释是正确的(它不是),那么这可能是clang ++中的一个“错误”,而不是Visual或g ++中的“错误”。在决定使用哪个编译器时,需要注意reinterpret_cast在编译器之间支持不太好,如果出于某种原因需要这样做,则绝对值得注意该行为。

编辑:

我刚刚意识到,这并没有解释为什么 b2 和 b3 也是 3(非布尔值)。我不认为把新的布尔值也视为8位整数是有意义的,无论 reinterpret_cast 如何,所以从一个声望为1的人那里得到这个结论。


当您告诉编译器将布尔值的地址重新解释为8位整数时,实际代码会将其解释为8位整数的地址。 - MSalters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接