Как определяется порядок вычислений в с
Перейти к содержимому

Как определяется порядок вычислений в с

  • автор:

О порядке вычисления выражений

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

Допустим, есть такой код

Как известно, оператор побитого сдвига вычисляется слева на право — но при вводе 1 2 вывод 2 1 (компилятор от майкрософта), с чем это связано ?

порождает ли данный код undefined behaviour или здесь всего лишь порядок вывода неопределён ?

Если убрать код чтения — всё довольно предсказуемо

Согласно стандарту C++ порядок вычисления аргументов функции не специфицирован, что означает, что компиляторы могут выбрать любой порядок вычисления аргументов Из стандарта C++ (1.9 Program execution)

3 Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function).

представляет собой цепочку вызовов функций operator << . Оно соответствет следующей цепочки вызовов функций с именем operator <<

MS VC++ вычисляет аргументы справа налево. Другой компилятор может вычислять аргументы в другом порядке, например, слева направо.

EDIT: Я приведу дополнительный наглядный пример на основе перегрузки оператора operator && .

Рассмотрите следующую демонстрационную программу

Ее вывод на консоль следующий

Если бы это был встроенный оператор operator && для фундаментальных типов, то выражение f2() не вычислялось бы в случае, если выражение f1() имело значение false .

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

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

Сравните вывод данной программы с выводом программы, в которой оператор operator && имеет дело с фундаментальными типами, то есть когда используется встроенный оператор operator && .

Вывод программы на консоль

https://amdy.su/wp-admin/options-general.php?page=ad-inserter.php#tab-8

Как видно, функция f2 никогда не будет вызвана, так как значение левого операнда равно false .

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

Order of evaluation

Order of evaluation of the operands of any C operator, including the order of evaluation of function arguments in a function-call expression, and the order of evaluation of the subexpressions within any expression is unspecified (except where noted below). The compiler will evaluate them in any order, and may choose another order when the same expression is evaluated again.

There is no concept of left-to-right or right-to-left evaluation in C, which is not to be confused with left-to-right and right-to-left associativity of operators: the expression f1() + f2() + f3() is parsed as (f1() + f2()) + f3() due to left-to-right associativity of operator+, but the function call to f3 may be evaluated first, last, or between f1() or f2() at run time.

Contents

[edit] Definitions

[edit] Evaluations

There are two kinds of evaluations performed by the compiler for each expression or subexpression (both of which are optional):

  • value computation: calculation of the value that is returned by the expression. This may involve determination of the identity of the object (lvalue evaluation) or reading the value previously assigned to an object (rvalue evaluation)
  • side effect: access (read or write) to an object designated by a volatile lvalue, modification (writing) to an object , atomic synchronization (since C11) , modifying a file, modifying the floating-point environment (if supported), or calling a function that does any of those operations.

If no side effects are produced by an expression and the compiler can determine that the value is not used, the expression may not be evaluated.

[edit] Ordering

«sequenced-before» is an asymmetric, transitive, pair-wise relationship between evaluations within the same thread (it may extend across threads if atomic types and memory barriers are involved).

  • If a sequence point is present between the subexpressions E1 and E2, then both value computation and side effects of E1 are sequenced-before every value computation and side effect of E2
  • If evaluation A is sequenced before evaluation B, then evaluation of A will be complete before evaluation of B begins.
  • If A is not sequenced before B and B is sequenced before A, then evaluation of B will be complete before evaluation of A begins.
  • If A is not sequenced before B and B is not sequenced before A, then two possibilities exist:
    • evaluations of A and B are unsequenced: they may be performed in any order and may overlap (within a single thread of execution, the compiler may interleave the CPU instructions that comprise A and B)
    • evaluations of A and B are indeterminably-sequenced: they may be performed in any order but may not overlap: either A will be complete before B, or B will be complete before A. The order may be the opposite the next time the same expression is evaluated.

    [edit] Rules

    [edit] Undefined behavior

    [edit] See also

    Operator precedence which defines how expressions are built from their source code representation.

    Как определяется порядок вычислений в с

    Арифметические операции производятся над числами. Значения, которые участвуют в операции, называются операндами. В языке программирования C++ арифметические операции могут быть бинарными (производятся над двумя операндами) и унарными (выполняются над одним операндом). К бинарным операциям относят следующие:

    Операция сложения возвращает сумму двух чисел:

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

    Операция вычитания возвращает разность двух чисел:

    Операция умножения возвращает произведение двух чисел:

    Операция деления возвращает частное двух чисел:

    При делении стоит быть внимательным, так как если в операции участвуют два целых числа, то дробная часть (при ее наличии) будет отбрасываться, даже если результат присваивается переменной float или double :

    Некоторые особенности при работе с числами с плавающей точкой

    При сложении или вычитании чисел с плавающей точкой, которые сильно отличаются по значению, следует проявлять осторожность. Например, сложим число 1.23E-4 ( 0.000123 ) и 3.65E+6 (3650000). Мы ожидаем, что сумма будет равна 3650000,000123 . Но при преобразовании в число с плавающей запятой с точностью до семи цифр это становится следующим

    Или соответствующий код на С++:

    То есть первое число никак не изменилось, поскольку для хранения точности отводится только 7 цифр.

    Также стоит отметить, что стандарт IEEE, который реализуется компиляторами С++, определяет специальные значения для чисел с плавающей точкой, в которых мантисса на бинарном уровне состоит только из нулей, а экспонента, которая состоит из одних единиц, в зависимости от знака представляет значения +infinity (плюс бесконечность +∞) и -infinity (минус бесконечность -∞). И при делении положительного числа на ноль, результатом будет +infinity , а при делении отрицательного числа на ноль — -infinity .

    Другое специальное значение с плавающей точкой, определенное этим стандартом, представляет значение NaN (не число). Это значение представляет результат операции, который не определяется математически, например, когда ноль делится на ноль или бесконечность на бесконечность. Результатом любой операции, в которой один или оба операнда являются NaN , также является NaN .

    Для демонстрации рассмотрим следующую программу:

    Инкремент и декремент

    Также есть две унарные арифметические операции, которые производятся над одним числом: ++ (инкремент) и — (декремент). Каждая из операций имеет две разновидности: префиксная и постфиксная:

    Увеличивает значение переменной на единицу и полученный результат используется как значение выражения ++x

    Хотя операции выполняются слева направо, но вначале будет выполняться операция инкремента ++b , которая увеличит значение переменной b и возвратит его в качестве результата, так как эта операция имеет больший приоритет. Затем выполняется умножение 5 * ++b , и только в последнюю очередь выполняется сложение a + 5 * ++b

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

    Так, и g++, и clang++ скомпилируют данный код, и результат переменной result будет таким, как в принципе и ожидается — 16, но компилятор clang++ также сгенерирует предупреждение.

    Как определяется порядок вычислений в с

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

    Операции в C (Си) и C++ выполняются в соответствии следующей таблице приоритетов операций. Приоритет означает, в каком порядке будут вычисляться операторы в выражении — если выше приоритет, то оператор будет вычисляться раньше. Таблица организована таким образом, что чем выше строка в таблице, тем выше приоритет. Операторы, которые присутствуют в одной ячейке, имеют одинаковый приоритет, и они будут вычисляться в том порядке, как они появляются в выражении, в указанном направлении. Для того, чтобы изменить приоритет вычисления операторов, могут быть применены круглые скобки.

    Несмотря на то, что приведенная таблица является вполне адекватной, в ней не учтены все тонкости языка. В частности имейте в виду, что тернарный оператор (15 строка в таблице) позволяет применять в среднем операнде любые произвольные выражения несмотря на то, что он перечислен с более высоким приоритетом, чем операции запятая и присваивание.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *