Я надеюсь, что кто-нибудь сможет процитировать главу и стих из недавнего стандарта Си; Я предполагаю, что он там, и я просто не смог его найти.
Раньше определение языка C позволяло компилятору вычислять ассоциативно эквивалентные выражения даже при наличии скобок. Таким образом, исходное заявление
a = (b + c) + d;
на самом деле можно оценить, добавив c и d, а затем добавив b к этому результату. (см .: K&R, 1-е издание, раздел 2.12, с.49). Эта формулировка была удалена во 2-м издании, но в нет специально сказано, что выражение должен должно оцениваться как заключенное в скобки. Насколько я понимаю, это было одной из причин введения хака «унарный +»: в статусе «a = + (b + c) + d;» унарный плюс заставит вычислить (b + c). В качестве альтернативы можно полагаться на определение точки последовательности и использовать несколько операторов:
tmp = b + c;
a = tmp + d;
и надеюсь, что чрезмерно агрессивный оптимизирующий компилятор, выполняющий прямую подстановку, не напортачит.
Я слышал, что он утверждал, что этот вид, если что-то больше не соответствует текущему стандарту (-ам) C, и что круглые скобки соблюдаются при оценке подвыражений. Мне не удалось найти явного утверждения на этот счет на языке действующего стандарта. В частности, в стандарте нет говорит что-то вроде того, что есть точка последовательности после заключенного в скобки подвыражения (что, вероятно, было бы излишне ограничивающей плохой идеей, но четко определило бы оценку).
Что вы имеете в виду под «облажаться»? Если переполнения нет, разницы между (b + c) + d и b + (c + d) нет. И если есть переполнение, у вас в любом случае неопределенное поведение.
Все это регулируется правилом «как будто» - компилятор может сгенерировать любой код, который дает тот же результат, что и в спецификации.
Итак, если я правильно вас понял, вы считаете, что правило «как если бы» позволяет оценивать эквивалентные выражения математически, даже при наличии круглых скобок? Конечно, так было в старые времена; вы верите, что это не изменилось?
Что касается «облажаться», каноническим примером является алгоритм суммирования Кахана, который включает такие строки, как «t = sum + y; c = (t - sum) - y;». Прямая подстановка даст "c = 0.0", что совсем не то, что вам нужно.
Они не могут этого изменить. Это слишком широко принято.
@bron Пришлось искать алгоритм Кахана. Википедия упоминает Стандарт ANSI C запрещает переупорядочивание, чтобы сделать C более подходящим для числовых приложений.
Я думаю, что речь идет об оптимизации отдельных операторов, а не скобках в одном выражении. Круглые скобки предназначены только для отмены приоритета оператора по умолчанию, а не для указания порядка операций.
Какие типы a, b, c и d? Если есть целые числа без знака, порядок не имеет значения, если есть целые числа со знаком и не генерируют переполнение, порядок не имеет значения, если они есть, то в любом случае у вас есть UB. Это изменчивые целые числа или числа с плавающей запятой?





Соответствующий раздел стандарта - C11 5.1.2.3 «Выполнение программы».
Подводя итог, C определяется в терминах абстрактной машины, которая производит наблюдаемое поведение, определение которой можно увидеть в пункте 6 этого раздела. (В основном вывод). Компилятор может делать с соответствующей программой все, что угодно, при условии, что сгенерированное наблюдаемое поведение соответствует наблюдаемому поведению, которое абстрактная машина будет производить для выполнения программы в соответствии со спецификацией языка.
В вашем примере добавление унарного + не влияет на наблюдаемое поведение, поэтому компилятор может его игнорировать.
В этом конкретном примере компилятор может изменить порядок добавления, поскольку он знает, что добавление нескольких операндов int дает один и тот же результат независимо от порядка (где «вызывает неопределенное поведение» считается одним и тем же результатом, если это делает основной порядок).
См. Также C11 §6.5 Выражения, где изложены некоторые правила порядка вычисления выражений (прежде чем углубляться в детали конкретных операторов).
Для ints, может быть, и так, но я никогда ничего не говорил о int. Такие вещи могут иметь значение для поплавков, например Алгоритм суммирования Кахана. И хотя унарный + не имеет никакого значения для значения выражения в математическом смысле, я понял, что часть первоначального обоснования унарного + хака заключалась в том, что его семантика была специально определена для принудительной оценки.
Совершенно верно, @bron. И там, где порядок оценки жестяная банка имеет значение, соответствующие реализации C должны соблюдать круглые скобки в этом отношении.
Мне очень хотелось бы верить, что это правда. Раньше это было именно нет true, и мне не удалось найти главу и стих в текущем стандарте, в которых говорится, что это поведение изменилось, и что требуется более разумная интерпретация для соответствия стандарту.
@bron такое же обоснование применимо к любой ситуации. Если (a+b)+c и a+(b+c) дадут точно такой же результат, компилятор может сделать то же самое. Если они дадут разные результаты, компилятор должен сделать первый. Для операций с плавающей запятой есть некоторая свобода действий, характерная для операций с плавающей запятой, что выходит за рамки этого ответа, если вы хотели конкретно спросить о переупорядочении операций с плавающей запятой, вам следует сосредоточить свой вопрос на этом (задайте новый вопрос или выполните поиск по этому сайту, как я уверен уже накрыли)
Другой ответ указывает на то, что реализациям C разрешена большая свобода в отношении деталей выполнения при условии, что наблюдаемое поведение программы совпадает с тем, что определено для абстрактной машины. Хотя это и верно, и актуально, существует риск создания ложного впечатления.
Во многих случаях реализация C не может полностью определить во время компиляции, какие отклонения от поведения абстрактной машины могут быть устранены без изменения наблюдаемого поведения. Для этого есть несколько причин, среди которых:
Следовательно, не следует интерпретировать свободу реализации, формальное определение которой служит в первую очередь для оптимизации реализации, как полную лицензию на изменение порядка оценки волей-неволей. На практике современные реализации C очень надежно избегают оптимизаций, которые могут изменить наблюдаемое поведение, по крайней мере, по умолчанию. Поскольку современные процессоры могут и действительно выполняют или упорядочивают выполнение по своему усмотрению, это оказывается довольно хорошим для наличия любых соответствующих реализаций вообще.
Если предположить, что реализации действительно соответствуют, тогда вопрос должен заключаться не в том, действительно ли оценка выражения является переупорядочена, а скорее в том, разрешает ли C переупорядочивать ее в появляться. То есть мне кажутся наиболее актуальными требования поведения абстрактной машины. Для этого наиболее важной частью стандарта является Раздел 6.5.
Особенно:
The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
а также
The grouping of operators and operands is indicated by the syntax.
Учитывая ваше примерное выражение
a = (b + c) + d;
, то это указывает, что сначала оцениваются подвыражения (b + c) и d, а затем вычисляется их сумма. Более ранние версии стандарта имели похожую формулировку, особенно в последней части, и я считаю бесспорным, что поведение абстрактной машины, определяемое каждой версией стандарта, требует того же.
Если вы не можете отличить (до предела наблюдаемого поведения), то действительно ли вам важно, какое изменение порядка выполняется? Есть причины, по которым вы можете это сделать - например, время выполнения является самым большим, - но вы не должны слишком беспокоиться о правильности.
Для получения результата, выраженного в исходном коде, требуется реализация C. Он может получить этот результат, используя любые вычисления, которые он выберет. Для этой цели результаты программы - это то, что стандарт C определяет как наблюдаемое поведение:
Если исходный код оценивает (a+b)+c и распечатывает его (так, чтобы результат был наблюдаемым), реализация C должна выдать результат, аналогичный добавлению a и b, а затем добавлению c, но это не требуется для получения этого результата с помощью добавление a и b, а затем добавление c.
Однако стандарт C позволяет вычислять выражения с плавающей запятой с большей точностью и диапазоном экспонент, чем их номинальные типы (например, арифметика double может использоваться для выражений, содержащих только операнды float), и он не определяет точность, требуемую для чисел с плавающей запятой. -точечная арифметика и библиотечные процедуры. Если вас беспокоит точное поведение выражений с плавающей запятой, вы должны учитывать не только порядок оценки; вы должны учитывать качество и свойства используемой вами реализации C.
Кроме того, некоторые компиляторы «C» не придерживаются стандарта C в отношении поведения с плавающей запятой. Опять же, вы должны учитывать качество и свойства используемой вами реализации C.
Стандарт C требует приведения или присваивания, чтобы «убрать» лишнюю точность. Итак, если вы напишете:
t = a+b;
printf("%.99g\n", t+c);
тогда компилятор должен выдать результат, как если бы (a+b)+c был оценен с некоторой точностью и затем округлен до своего номинального типа, а затем был добавлен c. Это может привести к ошибкам двойного округления, поскольку вычисление a+b с избыточной точностью может округляться таким образом, что последующее округление до номинального типа дает результат, отличный от добавления a+b только в номинальном типе.
Таким образом, если вам нужен точный контроль арифметики с плавающей запятой, вы не можете полагаться на стандарт C. Вы должны получить гарантии от вашего конкретного компилятора (например, переключить компилятор на использование только номинального типа) или использовать другой язык программирования.
Спасибо всем, кто ответил. Я ожидал, что в стандарте этот момент будет более ясным, но я думаю, что нет. Я резюмирую то, что я почерпнул из этого обсуждения, используя свой канонический пример алгоритма суммирования Кахана в качестве конкретного примера:
Рассмотрим следующее (err, item и totalSum имеют тип double):
err = ((nextItem + totalSum) - totalSum) - nextItem;
Здесь мы находимся в цикле, суммирующем элементы массива элементов. ЕСЛИ приведенный выше оператор на самом деле выполняется точно так, как написано, "err" будет содержать биты, которые в противном случае "упали бы с конца" из-за с ограниченной точностью (totalSum больше, чем nextItem).
Абстрактная машина C требует, чтобы выражение было вычислено «как если бы» было вычислено, как написано. C обычно допускает промежуточное выражение, которое будет оцениваться с более высокой точностью, а затем последний результат округлен по размеру. К сожалению, абстрактная машина C имеет бесконечную точность. Это означает, что является законный / соответствующий для реализация для оптимизации приведенного выше выражения в:
err = 0.0;
поскольку, если бы у вас была бесконечная точность, они были бы одинаковыми.
Конкретная реализация мая выбирает реализацию более строгих правил об оценке выражений (и, по общему признанию, всех реальных реализациях do), например, чтобы соответствовать семантике IEEE 754, но это не требуется по стандарту C.
ОДНАКО, стандартный делает требует, чтобы при присвоении или приведение, рассматриваемое значение должно быть преобразовано в правильное введите в этот момент. На практике это нарушает бесконечную точность парадигма. Итак, если мы напишем выражение так:
double tmp = nextItem + totalSum;
tmp = tmp - totalSum;
err = tmp - nextItem;
Здесь стандартные гарантии, что мы делать получаем желаемый эффект, поскольку, когда мы назначаем подвыражение tmp, значение должно быть округлено до подходящего размера, поэтому прямая подстановка не позволяет отменить из условий. Мы могли бы даже сделать это как:
err = ((double)((double)(nextItem + totalSum)) - totalSum) - nextItem;
что выглядит довольно странно, поскольку все переменные уже двойные, но приведение заставляет компилятор учитывать эффект ограниченного точность при оптимизации.
Действительно ли конкретная реализация Соответствие стандартам по этому поводу - это другой вопрос.
Я был бы очень удивлен, если бы это изменилось.