В скобках есть порядок оценки в (текущем) C? - PullRequest
0 голосов
/ 13 октября 2018

Я надеюсь, что кто-то может процитировать главу и стих из недавнего стандарта C;Я предполагаю, что он есть, и я просто не смог его найти.

В старые времена определение языка 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;

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

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

Ответы [ 4 ]

0 голосов
/ 17 октября 2018

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

Рассмотрим следующее (err, item и totalSum имеют тип «double»):

err = ((nextItem + totalSum) - totalSum) - nextItem;

Здесь мы находимся в цикле, суммируя элементы массива элементов.ЕСЛИ вышеприведенное утверждение фактически выполняется точно так, как написано, «err» будет содержать биты, которые иначе «выпали бы из конца» из-за ограниченной точности (totalSum велико по сравнению со nextItem).

Аннотация Cмашина требует, чтобы выражение оценивалось «как если бы», оно было вычислено как записанное.C обычно позволяет оценивать промежуточное выражение с более высокой точностью, а затем округлять конечный результат для соответствия.К сожалению, абстрактная машина C обладает бесконечной точностью.Это означает, что является легальным / соответствующим для реализации для оптимизации вышеприведенного выражения в:

err = 0.0;

, поскольку, если бы вы имели бесконечную точность, они были бы одинаковыми.

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

ОДНАКО, по стандарту требуется, чтобы при наличии присваивания или приведения соответствующее значение в этой точке было преобразовано в правильный тип.На практике это нарушает парадигму бесконечной точности.Поэтому, если мы напишем выражение следующим образом:

double tmp = nextItem + totalSum;
tmp = tmp - totalSum;
err = tmp - nextItem;

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

err = ((double)((double)(nextItem + totalSum)) - totalSum) - nextItem;

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

Является ли конкретная реализация на самом деле стандартом, соответствующим этому пункту, - это другой вопрос.

0 голосов
/ 13 октября 2018

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

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

  • программы, разбросанные по нескольким блокам перевода
  • зависимость программы от значений, определенных только во время выполнения
  • объекты, доступные через указатели/ возможный псевдоним указателя
  • длинные / сложные цепочки зависимостей

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

Если предположить, что реализации действительно соответствуют, то вопрос не долженбыть, действительно ли выражение выражения переупорядочено , а скорее разрешает ли C его переупорядочить .То есть именно требования к поведению абстрактной машины кажутся мне наиболее актуальными.Для этого наиболее важной частью стандарта является раздел 6.5 .

В частности:

Вычисления значений операндов оператора выполняются довычисление значения результата оператора.

и

Группировка операторов и операндов указывается синтаксисом.

Учитывая ваше примерное выражение

a = (b + c) + d;

, это означает, что сначала выполняются подвыражения (b + c) и d, а затем вычисляется их сумма.Более ранние версии стандарта имели схожие формулировки, особенно в последней части, и я думаю, что это бесспорный, что абстрактное поведение машины определяется каждой версии стандарта требует такой же.

1047 * Если вы не можете сказать разницу(до предела наблюдаемого поведения), тогда действительно ли вас волнует, что происходит переупорядочение?Есть причины, по которым вы могли бы это сделать - например, время выполнения является самым большим - но вам не следует слишком сильно беспокоиться о корректности.
0 голосов
/ 13 октября 2018

Реализация AC необходима для получения результата, выраженного в исходном коде.Он может получить этот результат, используя любые вычисления, которые он выберет.Для этого результаты программы - это то, что стандарт C определяет как наблюдаемое поведение :

  • Данные, записанные в файлы.
  • Динамика ввода и вывода интерактиваdevices.
  • Доступ к изменчивым объектам.

Если исходный код оценивает (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 только в номинальном типе.

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

0 голосов
/ 13 октября 2018

Соответствующим разделом стандарта является C11 5.1.2.3 «Выполнение программы» .

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

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

В этом конкретном примере компилятор может переупорядочить сложение, поскольку он знает, что делает сложение нескольких операндов intвыдает один и тот же результат независимо от порядка (где «вызывает неопределенное поведение» считается тем же результатом, если это делает основной порядок).

...