Я знаю, что неопределенное поведение потенциально может вызвать что угодно, что делает любую программу, содержащую UB, потенциально бессмысленной. Мне было интересно, если есть какой-либо способ определить самую раннюю точку в программе, что неопределенное поведение может вызвать проблемы.
Вот пример, иллюстрирующий мой вопрос.
void causeUndefinedBehavior()
{
//any code that causes undefined behavior
//every time it is run
char* a = nullptr;
*a;
}int main()
{
//code before call
//...
causeUndefinedBehavior();
//code after call
//...
}
Насколько я понимаю, возможное время, когда неопределенное поведение может быть вызвано (необязательно проявлено):
causeUndefinedBehavior()
компилируется.main()
компилируется.causeUndefinedBehavior()
выполнен.Или точка, в которой вызывается неопределенное поведение, совершенно различна для каждого случая и каждой реализации?
Кроме того, если я закомментировал строку, где causeUndefinedBehavior()
называется, это исключит UB, или это все еще будет в программе, так как код, содержащий UB, был скомпилирован?
Как показывает ваш код, неопределенное поведение почти всегда является условием состояние выполнения во время попытки поведения. Небольшая модификация вашего кода может сделать это до боли очевидным:
void causeUndefinedBehavior()
{
//any code that causes undefined behavior
//every time it is run
char* a = nullptr;
*a;
}int main()
{
srand(time(NULL));
//code before call
//...
if (rand() % 973 == 0)
causeUndefinedBehavior();
//code after call
//...
}
Вы можете выполнить это тысячу и более раз и никогда не отключать условие выполнения UB. это не меняет того факта, что сама функция явно UB, но обнаружения это во время компиляции в контексте вызывающего не тривиальный.
Я думаю, что это зависит от типа неопределенного поведения. Вещи, которые могут повлиять на что-то вроде смещения структуры, могут вызвать неопределенное поведение, которое будет отображаться при любом временном коде, касающемся этой структуры.
В целом, однако, большинство неопределенного поведения происходит в время выполнения, значение только в том случае, если этот код выполняется, произойдет неопределенное поведение.
Например, попытка изменить строковый литерал имеет неопределенное поведение:
char* str = "StackOverflow";
memcpy(str+5, "Exchange", 8); // undefined behavior
Это «неопределенное поведение» не будет иметь место до memcpy
выполняет. Он по-прежнему будет компилироваться в совершенно нормальный код.
Другой пример — пропуск возврата из функции с типом возврата, не являющимся пустым:
int foo() {
// no return statement -> undefined behavior.
}
Здесь, это в точке, в которой foo
возвращает, что происходит неопределенное поведение. (В данном случае на x86 все, что случилось в eax
регистр — результирующее возвращаемое значение функции.)
Многие из этих сценариев могут быть определены путем включения более высокого уровня сообщения об ошибках компилятора (например, -Wall
на GCC.)
«Неопределенное поведение» означает, что определение языка не говорит вам, что будет делать ваша программа. Это очень простое утверждение: нет информации. Вы можете спекулировать всем, что вам нравится, о том, что ваша реализация может или не может делать, но если ваша реализация не документирует, что она делает, вы только догадываетесь. Программирование не о гадании; это о знании. Если поведение вашей программы не определено, исправьте это.
хотя это «неопределенное поведение», заданное конкретным компилятором, оно будет иметь предсказуемое поведение некоторого вида. Но поскольку он не определен в разных компиляторах, это может привести к тому, что такое поведение будет происходить в любой точке компиляции / среды выполнения.
что делает любую программу, содержащую UB, потенциально бессмысленной
Не совсем верно. Программа не может «содержать» UB; когда мы говорим «UB», что сокращенно: поведение программы не определено. Все это!
Таким образом, программа не просто потенциально, но фактически бессмысленна с самого начала.
[intro.execution]/5
Соответствующая реализация, выполняющая правильно сформированную программу, должна производить то же наблюдаемое поведение, что и одно из возможных исполнений соответствующего экземпляра абстрактной машины с той же программой и тем же вводом. Тем не менее, если любое такое выполнение содержит неопределенную операцию, В этом международном стандарте нет требований к реализации, выполняющей эту программу с этим вводом (даже в отношении операций, предшествующих первой неопределенной операции).