我知道c++中的“未定义行为”可以让编译器做任何它想做的事情。然而,当我以为代码足够安全时,我却遇到了意外的崩溃。
在这种情况下,真正的问题只发生在使用特定编译器的特定平台上,而且只有启用了优化。
为了重现这个问题并最大限度地简化它,我尝试了几种方法。下面是一个名为Serialize
的函数的摘录,它将接受一个bool形参,并将字符串true
或false
复制到现有的目标缓冲区。
如果bool形参是一个未初始化的值,这个函数是否会在代码复查中,实际上没有办法判断它是否会崩溃?
// Zero-filled global buffer of 16 characters
char destBuffer[16];
void Serialize(bool boolValue) {
// Determine which string to print based on boolValue
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
const size_t len = strlen(whichString);
// Copy string into destination buffer, which is zero-filled (thus already null-terminated)
memcpy(destBuffer, whichString, len);
}
如果这段代码使用clang 5.0.0 +优化执行,它将/可能崩溃。
预期的三元运算符boolValue ? "true" : "false"
对我来说看起来足够安全,我假设,“无论boolValue
中的垃圾值是什么都无关紧要,因为它无论如何都会计算为真或假。”
我已经设置了一个编译器资源管理器示例,显示了拆卸中的问题,这里是完整的例子。# EYZ1
#include <iostream>
#include <cstring>
// Simple struct, with an empty constructor that doesn't initialize anything
struct FStruct {
bool uninitializedBool;
__attribute__ ((noinline)) // Note: the constructor must be declared noinline to trigger the problem
FStruct() {};
};
char destBuffer[16];
// Small utility function that allocates and returns a string "true" or "false" depending on the value of the parameter
void Serialize(bool boolValue) {
// Determine which string to print depending if 'boolValue' is evaluated as true or false
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
size_t len = strlen(whichString);
memcpy(destBuffer, whichString, len);
}
int main()
{
// Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
FStruct structInstance;
// Output "true" or "false" to stdout
Serialize(structInstance.uninitializedBool);
return 0;
}
问题是由优化器引起的:它很聪明地推断出字符串“true”和“false”的长度只差1。所以它不是真正计算长度,而是使用bool本身的值,应该技术上是0或1,并如下所示:
const size_t len = strlen(whichString); // original code
const size_t len = 5 - boolValue; // clang clever optimization
虽然这很“聪明”,但我的问题是:c++标准是否允许编译器假设bool类型只能有一个内部数字表示‘0’或‘1’,并以这样的方式使用它?
或者这是一种实现定义的情况,在这种情况下,实现假设它的所有bool只包含0或1,任何其他值都是未定义的行为领域?