是否应在分配时将布尔值截断为 true 或 false

Should a boolean value be truncated to either true or false when assigned?

本文关键字:true false 布尔值 分配 是否      更新时间:2023-10-16

>我发现存储在布尔变量(顺便说一句 Visual-C++ 和 clang++)中的值存在差异,在存储的值既不真也不假的情况下(如果它以某种方式损坏),我不确定这是一个视觉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;
}

这是视觉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++ 中有一个限制检查。

我应该忽略这一点,还是只有视觉C++才有的错误? 谢谢!

编辑:对于那些不了解示例目的的人来说,它只是一个展示,用于"模拟"内存损坏或代码另一部分中的错误,这些错误导致布尔值被初始化为真或假以外的其他东西,无论布尔值的二进制表示如何。

(我想知道我是否必须使用断言保护我的代码免受其他地方的不当使用,但前提是此行为不是 UB)

第二次编辑:添加了numeric_limits代码。

"在存储的值既不真也不假的情况下">

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

但事实上,GCC 和 MSVC 都没有将该位模式用于任一bool值。这使得它确实是未定义的行为。UB永远不能成为编译器错误。错误是指实现无法正常工作,但 UB 明确表示任何实际行为都是可以接受的。

该标准没有指定bool的值表示是什么。编译器可以自由地制定自己的规范。

您的证据表明,VC++ 要求true仅表示为 LSB 集,而 clang++ 允许true任何非零表示。

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

当未定义的行为发生时,没有任何保证;程序的所有输出都是毫无意义的。您无法根据在此点之后(甚至实际上在它之前)出现的输出语句来推断任何内容。

由于我真的没有在标准中找到有关从指针到布尔(或等效)的转换信息C++(如果定义了这些用法),我不愿意将其作为答案发布。但是,再三考虑,我不妨发布它 - 它可能会被其他人详细说明。

首先,C++14标准将bool定义为:

[基础.基础]

  1. 布尔类型的值为真或假。[注意:没有有符号、无符号、短布尔值或长布尔值类型或值。布尔类型的值参与积分促销 (4.5)

由于它参与整体促销,因此为其定义了以下促销:

[会议舞会]

    布尔类型的 prvalue
  1. 可以转换为 int 类型的 prvalue,false 变为零,true 变为 1。

并且,由于您正在使用std::ostream::operator<<进行打印,因此对于bool,它的定义如下:

[ostream.inserters.arithmetic]

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

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

[facet.num.put.virtuals]

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

由于您未在所示示例中使用boolalpha- 因此应应用典型的整体促销规则(如上所述)。

此外,我仍然无法解释为什么std::to_string(*pi)*pi = 3之后仍然3两种情况下打印,但它可能以某种方式与以下方面有关:

[重新诠释]

  1. [注意:reinterpret_cast执行的映射可能会或可能不会产生与原始值不同的表示形式。

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

这是我得到的输出:

B: 0 PB: 0
圆周率: 0 B: 3 铅: 3 圆周率: 3 b2: 3 b3: 3

据我了解(我是 c++ 编译器专家),reinterpret_cast指示编译器将位集合视为新类型。因此,当您告诉编译器将布尔值的地址重新解释为 8 位整数时,它实际上是将原始布尔值转换为 8 位整数(如果这有意义的话)。

因此,如果我的解释是正确的(事实并非如此),也许这是 clang++ 中的一个"错误",而不是 Visual 或 g++。 编译器之间reinterpret_cast不是很好支持,因此在决定使用哪个时,如果出于某种原因有必要,则此行为绝对值得注意。

编辑:

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