Подтвердить что ты не робот

Деление на ноль и undefined поведение в C

В этот документ является следующим примером фрагмента кода, который может вызвать деление на ноль:

if (arg2 == 0)
    ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO),
                    errmsg("division by zero")));
/* No overflow is possible */
PG_RETURN_INT32((int32) arg1 / arg2);

ereport Вот макрос, который расширяется до вызова функции bool -returning errstart, которая может возвращаться или не возвращаться и, условно (используя ?:) по ее возвращаемому значению, вызывает другая функция. В этом случае я считаю, что ereport с уровнем ERROR безоговорочно вызывает a longjmp() где-то еще.

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

Программист

[T] не смог сообщить компилятору, что вызов ereport (ERROR,:) не возвращается. Это означает, что деление всегда будет выполняться.

У Джона Реджера более простой пример:

void bar (void);
int a;
void foo3 (unsigned y, unsigned z)
{
  bar();
  a = y%z;
}

Согласно этому сообщению в блоге, clang поднимает модульную операцию над вызовом bar, и он показывает некоторый код сборки, чтобы доказать это.

Мое понимание C, поскольку оно относится к этим фрагментам, заключалось в том, что

  • Функции, которые не возвращаются или не возвращаются, хорошо сформированы в стандартном C, а декларации таких не требуют особых атрибутов, колоколов или свистов.

  • Семантика вызова функции, которая не возвращает или не может возвращаться, четко определена, в частности, 6.5.2.2 "Вызов функций" на C99.

  • Так как вызов ereport является полным выражением, в ; есть точка последовательности. Аналогично, поскольку вызов bar в коде Джона Реджера является полным выражением, в ; есть точка последовательности.

  • Следовательно, существует точка последовательности между вызовом ereport или bar и деление или по модулю.

  • Компиляторы C не могут вводить поведение undefined в программы, которые не вызывают поведение undefined самостоятельно.

Эти пять пунктов кажутся достаточными, чтобы сделать вывод о том, что вышеупомянутый тест деления на нуль правильно написан и что подъем по модулю выше вызова bar неверен. Два компилятора и множество экспертов не согласны. Что не так с моими рассуждениями?

4b9b3361

Ответ 1

Бумага неверна, а для примера clang это ошибка компилятора (довольно обычное явление с clang...). Хотелось бы, чтобы я мог дать вам более веские причины, но вы уже дали все правильные рассуждения в вопросе.

На самом деле, по проблеме clang, насколько я могу судить, ошибка пока не доказана. Поскольку bar возвращает в примере в блоге, к которому вы привязались, компилятор может свободно изменять порядок деления на вызов. Это тривиально, если bar определяется в одной и той же единицы перевода, но это также возможно с помощью LTO. Чтобы действительно проверить эту ошибку, вам нужна функция bar, которая никогда не возвращается.

Ответ 2

Использование правила "как будто"...

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

Это означает, что:

a) если деление на ноль (или деление, приводящее к переполнению) вызывает исключение (например, типичное для целочисленного деления на 80x86), то деление не может быть выполнено до вызова функции (если компилятор не может доказать, что деление всегда безопасно).

b) если деление на ноль (или деление, приводящее к переполнению) не вызывает исключения (например, типичного для с плавающей запятой на 80х86), компилятор может выполнить деление до вызова функции; до тех пор, пока ничего в вызываемой функции не может изменить значения, используемые в делении.