Является ли (x++, y) + (y++, x) неопределенным или неопределенным, и если он не указан, что он может вычислить?
Оператор последовательности запятой вводит точку последовательности в выражении. Мне интересно, означает ли это, что программа ниже избегает неопределенного поведения.
int x, y;
int main()
{
return (x++, y) + (y++, x);
}
Если он избегает неопределенного поведения, он все равно может быть неопределенным, то есть возвращать одно из нескольких возможных значений. Я думаю, что в C99, он может только вычислить 1
но на самом деле различные версии GCC компилируют эту программу в исполняемый файл, который возвращает 2
, Clang генерирует исполняемый файл, который возвращает 1
Видимо, согласен с моей интуицией.
Наконец, это что-то изменилось в C11?
4 ответа
Возьми выражение:
(x++, y) + (y++, x)
Оцените слева направо:
x++ // yield 0, schedule increment of x
, // sequence point: x definitely incremented now
y // yield 0
y++ // yield 0, schedule increment of y
// explode because you just read from y and wrote to y
// with no intervening sequence point
В стандарте нет ничего, что бы запрещало это, поэтому все поведение имеет неопределенное поведение.
Сравните этот псевдокод:
f() { return x++, y; }
g() { return y++, x; }
f() + g()
Согласно C99 (5.1.2.3/2) звонки f
а также g
сами по себе считаются побочными эффектами, а оператор вызова функции содержит точку последовательности непосредственно перед входом в функцию. Это означает, что выполнение функций не может чередоваться.
Под моделью "оценивать вещи параллельно":
f() // arbitrarily start with f: sequence point; enter f
g() // at the same time, start calling g: sequence point
С момента исполнения f
считается самим побочным эффектом, точка последовательности в g()
приостанавливает исполнение до f
вернулся. Таким образом, нет неопределенного поведения.
Вся глава 6.5 стандарта упоминает порядок оценки на основе оператора. Лучшее резюме порядка оценки, которое я смог найти, было (ненормативное) Приложение J стандарта:
C11 Приложение J
J.1 Неуказанное поведение
- Порядок, в котором оцениваются подвыражения, и порядок, в котором происходят побочные эффекты, за исключением случаев, указанных для операторов function-call (), &&, ||,?: И запятых (6.5)
В вашем примере вы не можете знать, является ли подвыражение (x++, y)
или же (y++, x)
оценивается первым, так как порядок вычисления операндов оператора + не указан.
Что касается неопределенного поведения, оператор запятой ничего не решил. Если (x++, y)
оценивается сначала, затем y
может быть оценен непосредственно перед y++
другого под-выражения. Поскольку к y обращаются дважды без промежуточной точки, для других целей, кроме определения значения для хранения, поведение не определено. Больше информации здесь.
Таким образом, ваша программа имеет неопределенное и неопределенное поведение.
(Кроме того, он имеет поведение, определяемое реализацией, поскольку int main(), а не int main (void) не является одной из четко определенных версий main в размещаемом приложении.)
Насколько я понимаю, любой из следующих порядков оценки является законным:
x++, y++, y, x
x++, y, y++, x
x++, y++, x, y
y++, x, x++, y
- ...
Но нет:
y, x++, x, y++
- ...
В моем понимании, единственное, что требуется, это (x++)
должен прийти раньше (y)
, а также (y++)
должен прийти раньше (x)
,
Поэтому я считаю, что это все еще неопределенное поведение.
Стандарт явно объявляет, какие операторы вводят точки последовательности, +
не среди них. Таким образом, ваше выражение может быть оценено таким образом, чтобы запрещенные подвыражения оценивались "близко" друг к другу, без точки последовательности между ними.
На прагматической основе существует причина для такого запрета, а именно, что оценка операндов такой операции может планироваться параллельно, например, если процессор имеет несколько конвейеров для работы рассматриваемых подвыражений. Вы можете легко убедить себя, что при такой модели оценки все может произойти, результат не предсказуем.
В вашем игрушечном примере конфликт предсказуем, но это не обязательно будет так, если вы будете использовать некоторую косвенность указателя в подвыражениях. Тогда вы не знаете, будет ли одно из подвыражений потенциально псевдонимом другого. Так что, в принципе, C мало что может сделать, если хочет разрешить этот тип оптимизации.