布尔值在分配时应该被截断为 true 还是 false?
Should a boolean value be truncated to either true or false when assigned?
我发现存储在 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++ 才有的错误?
谢谢!
编辑:
对于那些不理解示例用途的人,它只是 "simulate" 内存损坏或代码另一部分中的错误的展示,导致布尔值被初始化为 true 或 false 以外的东西,无论 bool.
的二进制表示形式如何
(我想知道我是否必须使用断言来保护我的代码不被其他地方不当使用,但前提是这种行为不是 UB)
第二次编辑:
添加了 numeric_limits 代码。
"in the case where the stored value is neither true nor false"
你认为为什么会这样? C++不限制bool
的二进制表示。在某些编译器上,true
可以表示为 00000011
,而其他编译器可以选择将 false
表示为 00000011
.
但实际上,无论是 GCC 还是 MSVC 都没有将那个位模式用于任一 bool
值。这确实使它成为未定义的行为。 UB 可以 永远不会 成为编译器错误。错误是指实现无法正常工作,但 UB 具体表示任何实际行为都是可以接受的。
不确定这是否有帮助,但 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++ 中的 "bug",而不是 Visual 或 g++。 reinterpret_cast
编译器之间的支持不是很好,因此在决定使用哪个时这种行为绝对值得注意,如果出于某种原因这是必要的。
编辑:
我刚刚意识到这并不能解释为什么 b2 和 b3 也是 3(非布尔值)。我不认为将新布尔值也视为 8 位整数是有意义的,不管 reinterpret_cast
,所以从一个有 1 个代表的人那里得到它的价值:)
因为我真的没有在 C++ 标准中找到关于从指针到布尔(或等效物)的转换的信息(如果定义了它们的用法),所以我不愿意 post this as答案。但是,转念一想,我还是 post 它 - 它可能会被其他人详细阐述。
首先,C++14标准将bool
定义为:
[basic.fundamental]
- Values of type bool are either true or false. [Note:There are no signed, unsigned, short, or long bool types or values. — end note] Values of type bool participate in integral promotions (4.5)
由于参与积分促销,为其定义如下促销:
[conv.prom]
- A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true becoming one.
而且,由于你是用std::ostream::operator<<
打印,对于bool
,它的定义如下:
[ostream.inserters.arithmetic]
- The classes num_get<> and num_put<> handle locale-dependent numeric formatting and parsing.
由于它使用 num_put<>
作为实际输出,因此与 bool
输出相关的片段定义为:
[facet.num.put.virtuals]
- If (str.flags() & ios_base::boolalpha) == 0 returns do_put(out, str, fill, (int)val)
由于您在示例中没有使用 boolalpha
- 应适用典型的积分提升规则(如上所述)。
此外,我仍然无法解释为什么 std::to_string(*pi)
在 *pi = 3
之后仍然打印 3
在这两种情况下,但它可能以某种方式与以下内容有关:
[expr.reinterpret.cast]
- [Note: The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value.— end note]
标准没有具体说明 bool
的值表示是什么。编译器可以自由制定自己的规范。
您的证据表明 VC++ 要求 true
仅表示为 LSB 集,而 clang++ 允许任何非零表示为 true
.
对于 VC++,您的代码在 bool const b2{ b };
行导致未定义的行为,特别是当它试图从 b
中读取值时。 b
的存储中设置的位不对应于 b
的值,并且标准没有定义在这种情况下会发生什么,因此它是未定义的行为。
当未定义的行为发生时,没有任何保证;该程序的所有输出都是无意义的。您无法根据出现在该点之后(实际上什至之前)的输出语句推断出任何内容。
我发现存储在 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++ 才有的错误? 谢谢!
编辑: 对于那些不理解示例用途的人,它只是 "simulate" 内存损坏或代码另一部分中的错误的展示,导致布尔值被初始化为 true 或 false 以外的东西,无论 bool.
的二进制表示形式如何(我想知道我是否必须使用断言来保护我的代码不被其他地方不当使用,但前提是这种行为不是 UB)
第二次编辑: 添加了 numeric_limits 代码。
"in the case where the stored value is neither true nor false"
你认为为什么会这样? C++不限制bool
的二进制表示。在某些编译器上,true
可以表示为 00000011
,而其他编译器可以选择将 false
表示为 00000011
.
但实际上,无论是 GCC 还是 MSVC 都没有将那个位模式用于任一 bool
值。这确实使它成为未定义的行为。 UB 可以 永远不会 成为编译器错误。错误是指实现无法正常工作,但 UB 具体表示任何实际行为都是可以接受的。
不确定这是否有帮助,但 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++ 中的 "bug",而不是 Visual 或 g++。 reinterpret_cast
编译器之间的支持不是很好,因此在决定使用哪个时这种行为绝对值得注意,如果出于某种原因这是必要的。
编辑:
我刚刚意识到这并不能解释为什么 b2 和 b3 也是 3(非布尔值)。我不认为将新布尔值也视为 8 位整数是有意义的,不管 reinterpret_cast
,所以从一个有 1 个代表的人那里得到它的价值:)
因为我真的没有在 C++ 标准中找到关于从指针到布尔(或等效物)的转换的信息(如果定义了它们的用法),所以我不愿意 post this as答案。但是,转念一想,我还是 post 它 - 它可能会被其他人详细阐述。
首先,C++14标准将bool
定义为:
[basic.fundamental]
- Values of type bool are either true or false. [Note:There are no signed, unsigned, short, or long bool types or values. — end note] Values of type bool participate in integral promotions (4.5)
由于参与积分促销,为其定义如下促销:
[conv.prom]
- A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true becoming one.
而且,由于你是用std::ostream::operator<<
打印,对于bool
,它的定义如下:
[ostream.inserters.arithmetic]
- The classes num_get<> and num_put<> handle locale-dependent numeric formatting and parsing.
由于它使用 num_put<>
作为实际输出,因此与 bool
输出相关的片段定义为:
[facet.num.put.virtuals]
- If (str.flags() & ios_base::boolalpha) == 0 returns do_put(out, str, fill, (int)val)
由于您在示例中没有使用 boolalpha
- 应适用典型的积分提升规则(如上所述)。
此外,我仍然无法解释为什么 std::to_string(*pi)
在 *pi = 3
之后仍然打印 3
在这两种情况下,但它可能以某种方式与以下内容有关:
[expr.reinterpret.cast]
- [Note: The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value.— end note]
标准没有具体说明 bool
的值表示是什么。编译器可以自由制定自己的规范。
您的证据表明 VC++ 要求 true
仅表示为 LSB 集,而 clang++ 允许任何非零表示为 true
.
对于 VC++,您的代码在 bool const b2{ b };
行导致未定义的行为,特别是当它试图从 b
中读取值时。 b
的存储中设置的位不对应于 b
的值,并且标准没有定义在这种情况下会发生什么,因此它是未定义的行为。
当未定义的行为发生时,没有任何保证;该程序的所有输出都是无意义的。您无法根据出现在该点之后(实际上什至之前)的输出语句推断出任何内容。