В C есть ли гарантия с кодом до неопределенного поведения? - PullRequest
11 голосов
/ 23 октября 2010

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

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

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

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

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

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

Ответы [ 4 ]

8 голосов
/ 23 октября 2010

Что ж, даже если игнорировать такие вещи, как «Все может произойти! Программа может вернуться во времени и вообще не запускаться!», Компилятор может обнаружить некоторые формы неопределенного поведения и не скомпилировать его. В этом случае вы бы не получили его запустить в первую очередь. Так что да, неопределенное поведение в принципе заразно, если не всегда так на практике.

5 голосов
/ 23 октября 2010

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

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

Опять же, я полагаю, что возможно, что неопределенное поведение, выполненное после завершенных, четко определенных действий, может нанести ущерб в той степени, в которой кажется, что четко определенное поведение не завершилось правильно. Я думаю, что-то вроде одного из тех вещей, «если дерево падает в лесу…».


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

Вот что говорит стандарт 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.

Я не думаю, что изменение объекта более одного раза между точками последовательности является «неопределенным поведением» во время перевода, поскольку объекты не доступны / не изменены во время перевода.

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

#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;
}

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

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

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

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

3 голосов
/ 23 октября 2010

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

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

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

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

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

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

0 голосов
/ 23 октября 2010

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

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

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

...