В C99, является ли f ()+g () неопределенным или просто неопределенным?


я раньше думал, что в C99, даже если побочные эффекты функций f и g вмешался, и хотя выражение f() + g() не содержит точки последовательности,f и g будет содержать некоторые, поэтому поведение будет неопределенным: либо f() будет вызываться перед g(), либо g() перед f().

Я уже не так уверен. Что делать, если компилятор inlines функции (которые компилятор может решить сделать, даже если функции не объявлены inline) и затем переупорядочивает инструкции? Можно ли получить результат, отличный от вышеперечисленных двух? Другими словами, это неопределенное поведение?

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

3 54

3 ответа:

выражение f() + g() содержит минимум 4 точки последовательности; один перед вызовом f() (после того, как все нулевые его аргументы вычисляются); один перед вызовом g() (ведь нуль его аргументов вычисляется); один как вызов f() возвращает; и как вызов g() возвращает. Далее, две точки последовательности, связанные с f() происходят либо до, либо после двух точек последовательности, связанных с g(). Что нельзя сказать что упорядочение точек последовательности будет происходить в - Независимо от того, происходят ли точки f перед точками g или наоборот.

даже если компилятор встроил код, он должен подчиняться правилу "как будто" - код должен вести себя так же, как если бы функции не были перемежены. Это ограничивает область повреждения (предполагая, что компилятор без ошибок).

Итак последовательность в которой f() и g() оцениваются неопределенно. Но все остальное довольно чистый.


в комментарии supercat спрашивает:

я ожидал бы, что вызовы функций в исходном коде остаются точками последовательности, даже если компилятор сам решает их встроить. Остается ли это верным для функций, объявленных "inline", или компилятор получает дополнительную широту?

я считаю, что правило "как будто" применяется, и компилятор не получает дополнительной широты для пропуска точек последовательности, потому что он использует явно

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

@dmckee

Ну, это не будет вписываться в комментарий, но вот что:

во-первых, вы пишете правильные статический анализатор. "Правильно" в этом контексте означает, что он не будет молчать, если есть что-то сомнительное в анализируемом коде, поэтому на этом этапе вы весело объединяете неопределенное и неопределенное поведение. Они оба плохи и неприемлемы в критическом коде, и вы предупреждаете, справедливо, для них обоих.

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

таким образом, вы хотите выдать одно предупреждение для

*p = x;
y = *p;

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

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

тогда, вы поймете, что не многие люди пишут критический код, поэтому вы делаете другие, легкие анализы для остальных из них, основанные на результатах первоначального, правильного анализа. Скажем, программа c slicer.

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

и пользователи весело игнорировать сигналы тревоги и использовать срез.

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

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

и это конец истории, которая определенно не вписывается в комментарий. Извиняюсь перед всеми, кто читал так далеко.