В C есть ли какая-либо гарантия с кодом до поведения undefined?

В следующем коде гарантируется печать "0\n"?

#include <stdio.h>
int main(void)
{
    int c = 0;
    printf("%d\n",c);

    printf("%d,%d\n",++c,++c);
}

В общем случае, если программа имеет undefined поведение, вся программа становится undefined или только из точки последовательности, которая начинает проблемный код?

Обратите внимание: я не спрашиваю, что делает компилятор со вторым printf. Я спрашиваю, гарантируется ли первый printf.

Я знаю, что поведение undefined способно взорвать ваш компьютер, сбой вашей программы или что-то еще.

Ответ 1

Ну, даже игнорируя такие вещи, как "Может случиться что-нибудь, программа может вернуться назад вовремя и предотвратить запуск в первую очередь!", вполне возможно, что компилятор может обнаружить некоторые формы поведения undefined, а не компилировать в этом случае, в этом случае вы не получили бы его в первую очередь. Так что да, undefined поведение является заразным в принципе, если не обязательно так на практике большую часть времени.

Ответ 2

Все, что было сделано программой до того, как оно вызывает поведение undefined, конечно, уже сделано.

Итак, printf() отправил бы "0\n" в поток stdout. Независимо от того, действительно ли эти данные привели его к устройству, зависит от того, не является ли этот поток небуферизованным, буферизованным или буферизированным по строке.

И снова я полагаю, что возможно, что поведение undefined, выполняемое после выполненных, четко определенных действий, может привести к повреждению в той степени, в которой кажется, что корректное поведение не было выполнено правильно. Наверное, вроде как один из тех, "если дерево падает в лесу...".


Обновите, чтобы понять, что будущее поведение undefined означает, что все ставки отключены даже до запуска программы...

Здесь, что стандарт C99 должен сказать об изменении значения объекта более одного раза между точками последовательности:

Между предыдущей и следующей точкой последовательности объект должен иметь сохраненное значение не более чем один раз, оценивая выражение.

И стандарт также имеет это сказать о доступе к объекту:

Доступ

 <execution-time action> to read or modify the value of an object
 NOTE 1   Where only one of these two actions is meant, ``read'' or ``modify'' is used.
 NOTE 2   "Modify'' includes the case where the new value being stored is the same as the previous value.
 NOTE 3   Expressions that are not evaluated do not access objects.

Я не думаю, что модификация объекта более одного раза между точками последовательности - это "undefined поведение" во время трансляции, поскольку объекты не доступны или изменены во время перевода.

Тем не менее, я согласен с тем, что компилятор, который диагностирует это поведение undefined во время компиляции, будет хорошим, но я также думаю, что этот вопрос более интересен, если его применять только к программам, которые были успешно скомпилированы. Поэтому немного измените вопрос, чтобы дать ситуацию, когда компилятор не может диагностировать поведение undefined во время перевода:
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    int c[] = { 0, 1, 2, 3 };
    int *p1 = &c[0];
    int *p2 = &c[1];

    if (argc > 1) {
        p1 = &c[atoi(argv[1])];
    }
    if (argc > 2) {
        p2 = &c[atoi(argv[2])];
    }

    printf("before: %d, %d\n", *p1, *p2);

    printf("after:  %d, %d\n", ++(*p1),++(*p2)); /* possible undefined behavior */

    return 0;
}

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

Итак, давайте зададим тот же вопрос с этой программой: что стандарт говорит о том, что может произойти с результатами первого printf() или побочными эффектами?

Если входы обеспечивают допустимые значения индекса, поведение undefined может произойти только после первого printf(). Предположим, что вход argv[1] == "1" и argv[2] == "1": реализация компилятора не имеет права определять перед первым printf(), поскольку с тех пор, как поведение undefined произойдет в какой-то момент программы, ему разрешено пропустить первый printf() и перейдите к его undefined поведению форматирования жесткого диска (или, возможно, других ужасов).

Учитывая, что компилятор согласен согласиться на перевод программы, обещание будущего поведения undefined не дает компилятору свободы делать все, что захочет, до того, как поведение undefined действительно имеет место. Конечно, как я уже упоминал ранее, ущерб, нанесенный поведением undefined, может привести к разрушению предыдущих результатов, но эти результаты должны были произойти.

Ответ 3

Undefined поведение зависит от поставщика компилятора/случайного случая. Это означает, что это может вызвать исключение, испорченные данные в вашей программе, писать через вашу mp3-коллекцию, вызвать ангела или загорать вашу бабушку. Если у вас есть поведение undefined, вся ваша программа станет undefined.

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

Если в программе есть поведение undefined, вся программа становится undefined или только из точки последовательности, которая начинает проблемный код?

Код, выполняющийся до точки undefined, вероятно, поступил правильно. Но это только делает так много хорошего. Как только вы нажмете undefined поведение, буквально все может случиться. Будет ли что-то происходить, покрывается Закон Мерфи:)

Оптимизации полагаются на четко определенное поведение и играют всевозможные трюки за пределами этого поля, чтобы получить скорость. Это означает, что ваш код может полностью выходить из строя, пока побочные эффекты не будут неотличимы для хорошо определенной программы. Просто потому, что поведение undefined, похоже, начинается в определенный момент вашего исходного кода, не гарантирует, что любые предыдущие строки кода будут защищены. С включенными оптимизациями ваш код может очень легко поразить поведение undefined намного раньше.

Пища для размышлений: эксплойты переполнения буфера, реализованные различными типами вредоносных программ, в значительной степени зависят от поведения undefined.

Ответ 4

Для поведения undefined следует, по-видимому, различать вещи, которые можно обнаружить во время компиляции (как и в вашем случае), и вещи, зависящие от данных, и которые происходят только во время выполнения, например, случайно записывая объект const.

Где для более поздней программы программа должна работать до тех пор, пока не произойдет UB, потому что она обычно не может ее заранее обнаружить (проверка модели - непростая задача для нетривиальных программ), для вашего случая может быть разрешено создавать любые программа, которая, например, отправляет деньги поставщику компилятора или так; -)

Более разумным выбором было бы производить только ничего, то есть бросать ошибку, а не компилировать вообще. Некоторые компиляторы делают это, когда им говорят так, например, с помощью gcc вы получаете это с помощью -Wall -std=c99 --pedantic -Werror.