C++ Какое самое раннее неопределенное поведение может проявиться?

Я знаю, что неопределенное поведение потенциально может вызвать что угодно, что делает любую программу, содержащую 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
 //...
}

Насколько я понимаю, возможные моменты неопределенного поведения могут быть вызваны (не обязательно проявлены):

  1. Когда causeUndefinedBehavior() компилируется.
  2. Когда main() компилируется.
  3. Во время работы программы.
  4. В момент выполнения causeUndefinedBehavior().

Или точка, в которой вызывается неопределенное поведение, совершенно разная для каждого случая и каждой реализации?

Кроме того, если я закомментирую строку, где вызывается causeUndefinedBehavior(), устранит ли это UB, или он все еще будет в программе, поскольку код, содержащий UB, был скомпилирован?


person Elliot Hatch    schedule 29.10.2012    source источник


Ответы (5)


Как показывает ваш код, неопределенное поведение почти всегда является условием состояния среды выполнения во время попытки поведения. Небольшая модификация вашего кода может сделать это до боли очевидным:

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, но обнаружение ее во время компиляции в контексте вызывающей программы не является тривиальной задачей.

person WhozCraig    schedule 29.10.2012
comment
Хм, но разве компилятор теоретически не может решить, что, поскольку causeUndefinedBehaviour() вызывает неопределенное поведение, то он никогда не должен вызываться в правильной программе, и поэтому решить, что if безопасно отбросить. - person Mankarse; 29.10.2012
comment
@Mankarse, очень хороший момент. Примените то, что находится в функции main(), к функции caseUndefinedBehaviour() (назовите ее mayCauseUndefinedBehavior()). Т.е. если он тоже условно может иметь и UB... дело в том, что без запуска вы не можете узнать (не смотря на полную интерпретацию выброса, которая насколько я понимаю, "работает"). - person WhozCraig; 29.10.2012

Я думаю, это зависит от типа неопределенного поведения. Вещи, которые могут повлиять на что-то вроде смещения структуры, могут вызвать неопределенное поведение, которое будет проявляться каждый раз, когда код, касающийся этой структуры, выполняется.

Однако в целом неопределенное поведение в большинстве случаев происходит во время во время выполнения, то есть неопределенное поведение возникает только в том случае, если этот код выполняется.

Например, попытка изменить строковый литерал имеет неопределенное поведение:

char* str = "StackOverflow";
memcpy(str+5, "Exchange", 8);    // undefined behavior

Это «неопределенное поведение» не произойдет, пока не будет выполнено memcpy. Он по-прежнему будет компилироваться в совершенно нормальный код.

Другой пример — пропуск возврата из функции с непустым типом возврата:

int foo() {
    // no return statement -> undefined behavior.
}

Здесь именно в тот момент, когда foo возвращает значение, возникает неопределенное поведение. (В этом случае на x86 все, что оказалось в регистре eax, является результирующим возвращаемым значением функции.)

Многие из этих сценариев можно определить, включив более высокий уровень отчетов об ошибках компилятора (например, -Wall в GCC.)

person Jonathon Reinhart    schedule 29.10.2012
comment
-Wall не является максимальным уровнем сообщения об ошибках компилятора. Этот суффикс all не означает все предупреждения. Есть много предупреждений, которые не включены -Wall. Лучшее представление состоит в том, что -Wall — это минимальный уровень разумного сообщения об ошибках. - person David Hammen; 29.10.2012
comment
Спасибо, Дэвид. Я изменил формулировку, но мне нравится и ваша точка зрения. - person Jonathon Reinhart; 29.10.2012

«Неопределенное поведение» означает, что определение языка не говорит вам, что будет делать ваша программа. Это очень простое утверждение: нет информации. Вы можете сколько угодно рассуждать о том, что ваша реализация может делать, а что нет, но если ваша реализация не документирует, что она делает, вы только догадываетесь. Программирование — это не догадки; это о знании. Если поведение вашей программы не определено, исправьте это.

person Pete Becker    schedule 29.10.2012
comment
Все это правда, но не отвечает на вопрос ОП. - person Jonathon Reinhart; 30.10.2012
comment
@JonathonReinhart - правильно. Это не ответ на вопрос, потому что у вопроса нет ответа. - person Pete Becker; 30.10.2012
comment
Самый простой ответ — во время выполнения рассматриваемого неопределенного кода. - person Jonathon Reinhart; 30.10.2012
comment
@JonathonReinhart - это просто, но неправильно. В определении языка сказано, что поведение программы не определено, если она выполняет различные действия. Это не говорит о том, что вы можете запустить программу до того момента, когда вы сделали что-то не так. Это даже не требует, чтобы программа компилировалась и создавала исполняемый файл. Да, есть аргументы вроде того, сколько ангелов может танцевать на булавочной головке, но неопределенное поведение означает, что поведение программы не определено. Полная остановка. - person Pete Becker; 30.10.2012
comment
Программа может демонстрировать неопределенное поведение на некоторых или всех входных данных. Если он не определен только для некоторых входов, поведение все еще определено для остальных входов. - person Demi; 06.07.2013
comment
@Demetri: сказать что-то не значит сделать это правдой. Прежде чем вы скажете миру, что Пит Беккер знает о C++ меньше, чем вы, найдите некоторые фактические подтверждающие доказательства. - person Tony Delroy; 12.06.2014
comment
Пример @TonyD: int main(int argc, char** argv) { if (argc == 1) { void* x = 0; x = 0 // неопределенное поведение / } else { return 0; // хорошо определенный */ } } имеет неопределенное поведение тогда и только тогда, когда argc == 1 - person Demi; 12.06.2014
comment
@Demetri: см. выше - сказать что-то не значит сделать это правдой. Если у вас есть *x в вашем коде, а компилятор знает, что статически x является 0, он может не продолжить процесс генерации наивно подразумеваемого кода, который бы он делал, когда не знал о значении времени выполнения x. Учитывая поведение в undefined, можно не генерировать объект/исполняемый файл или генерировать его с поддельным машинным кодом с предупреждением или без него. Это мое утверждение - надеюсь, оно ясное - если вы просто не согласны, не имея какого-то раздела Стандарта или FAQ от Страуструпа или чего-то подобного, тогда давайте согласимся не соглашаться.... - person Tony Delroy; 13.06.2014

хотя это «неопределенное поведение», с учетом конкретного компилятора оно будет иметь какое-то предсказуемое поведение. Но поскольку он не определен, на разных компиляторах это может привести к такому поведению в любой момент компиляции / выполнения.

person Keith Nicholas    schedule 29.10.2012
comment
Иногда это действительно непредсказуемо, например, если это зависит от значения неинициализированной памяти. - person Mark Ransom; 29.10.2012
comment
Можете ли вы привести пример UB во время компиляции? - person BЈовић; 12.06.2014
comment
УБ произойти не может. - person Lightness Races in Orbit; 28.02.2018

что делает любую программу, содержащую UB, потенциально бессмысленной

Не совсем правильно. Программа не может «содержать» УБ; когда мы говорим «UB», это сокращение от: поведение программы не определено. Все это!

Так что программа изначально не только потенциально, но фактически бессмысленна.

[intro.execution]/5: Соответствующая реализация, выполняющая правильно сформированную программу, должна давать то же наблюдаемое поведение, что и одно из возможных исполнений соответствующего экземпляра абстрактной машины с той же программой и теми же входными данными. Однако, если какое-либо такое выполнение содержит неопределенную операцию, данный международный стандарт не предъявляет требований к реализации, выполняющей эту программу с такими входными данными (даже в отношении операций, предшествующих первой неопределенной операции).

person Lightness Races in Orbit    schedule 28.02.2018