Я знаю, что "неопределенное поведение" в 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? "true": "false"
выглядело достаточно безопасно для меня, я предполагал: "Независимо от того, boolValue
значение мусора находится в boolValue
не имеет значения, так как оно все равно будет иметь значение true или false".
Я настроил пример Compiler Explorer, который показывает проблему в разборке, вот полный пример. Примечание: чтобы воспроизвести проблему, я обнаружил, что сработала комбинация с использованием Clang 5.0.0 с оптимизацией -O2.
#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;
}
Проблема возникает из-за оптимизатора: было достаточно умно сделать вывод, что строки "истина" и "ложь" отличаются только по длине на 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, а любое другое значение является неопределенной территорией поведения?