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
。
逆アセンブリの問題を示すコンパイラ エクスプローラーの例をセットアップしました。ここに完全な例を示します。注:問題を再現するために、私が見つけた組み合わせは、-O2最適化でClang 5.0.0を使用することです。
#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' の内部数値表現のみを持つことができ、そのような方法で使用できると想定できますか?
または、これは実装定義のケースですか。その場合、実装はすべてのブール値が 0 または 1 のみを含み、他の値は未定義の動作領域であると想定していましたか?