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

Как будет вычислено выражение a b

  • автор:

Как вычисляется выражение a+++b? — C++ — Ответ 6238388

ЦитатаСообщение от taras atavin Посмотреть сообщение

Нет. Человек в этом месте видит:

Я говорила о ПРОСТЫХ примерах и речь шла о направлении «чтения» выражения. Если я напишу а+б, разве Вы увидите в этом +аб или ба+ ? Нет. Увидите слева направо а+б. А потом уже описывала, что будет дальше.

Меню пользователя @ Тамика
Читать блог

94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Готовые ответы и решения:

Как вычисляется выражение
Как вычисляется выражение while? char *str1; char *str2; . while(*str2++ = *str1++); .

Неверно вычисляется выражение
Не до конца сделал программу.Суть в том в "sbichne" выводиться 0 в любом случае.До некоторых.

Неправильно вычисляется логическое выражение
В этом отрывке программы у меня программа не замечает условие,почему? То есть она выводит не для.

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

87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь

как вычисляется следующее выражение
не знаю как использовать редактор формул поэтому сначала как звучит задание Для натуральных.

Как вычисляется это выражение?
Я только начала изучать js и у меня появился вопрос. var a = 1, b = 1, c, d; c = ++a;.

Неверно вычисляется выражение
Помогите пожалуйста с примером! Console.WriteLine("Здравствуйте!"); double x, y, z.

Неправильно вычисляется выражение
Выводит 0,834370587875676 в результате, но должно выводить 0,7121 Работаю в Borland Builder C++ 6.

Как вычисляется выражение a+++b?

Author24 — интернет-сервис помощи студентам

Здравствуйте.
Как вычисляется выражение a+++b?
Как компилятор понимает, что означает +++: ++ + или + ++? Как он разбивает последовательность из трёх плюсов на два оператора? Есть понятия приоритета операций и ассоциативности, но они применяются уже к определённым операторам. А в данном случае как компилятор понимает, какой оператор использовался?

Добавлено через 2 минуты
Следующий код:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
void main(void){ int a,b,c; a=10,b=100,c=1000; printf("a=10,b=100,c=1000;\na=%d, b=%d, c=%d\n\n",a,b,c); c=a+++b; printf("c=a+++b;\na=%d, b=%d, c=%d\n\n",a,b,c); a=10,b=100,c=1000; printf("a=10,b=100,c=1000;\na=%d, b=%d, c=%d\n\n",a,b,c); c=(a++)+b; printf("c=(a++)+b;\na=%d, b=%d, c=%d\n\n",a,b,c); a=10,b=100,c=1000; printf("a=10,b=100,c=1000;\na=%d, b=%d, c=%d\n\n",a,b,c); c=a+(++b); printf("c=a+(++b);\na=%d, b=%d, c=%d\n\n",a,b,c); }

я выполнил в онлайн-компиляторе C: http://codepad.org/CtpEfoHy
Вот вывод программы:

a=10,b=100,c=1000; a=10, b=100, c=1000 c=a+++b; a=11, b=100, c=110 a=10,b=100,c=1000; a=10, b=100, c=1000 c=(a++)+b; a=11, b=100, c=110 a=10,b=100,c=1000; a=10, b=100, c=1000

Из данного кода ясно, что данный компилятор воспринимает +++ как ++ +. Но объясните: почему? Чем он руководствуется? Оговорено ли это в стандарте C? Или это поведение меняется от компилятора к компилятору?

Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:

Как вычисляется выражение
Как вычисляется выражение while? char *str1; char *str2; . while(*str2++ = *str1++); .

Неверно вычисляется выражение
Не до конца сделал программу.Суть в том в "sbichne" выводиться 0 в любом случае.До некоторых.

Неправильно вычисляется логическое выражение
В этом отрывке программы у меня программа не замечает условие,почему? То есть она выводит не для.

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

Котовчанин
942 / 482 / 200
Регистрация: 16.02.2010
Сообщений: 3,338
Записей в блоге: 37

(a++) + b

Регистрация: 27.06.2013
Сообщений: 21

Тамика, под моими словами: «Как компилятор понимает, какой оператор использовался»*— я подразумевал не просто: «Какой оператор использовался?»*— а: «Почему компилятор использовал тот или иной оператор?»

Котовчанин
942 / 482 / 200
Регистрация: 16.02.2010
Сообщений: 3,338
Записей в блоге: 37

Читая слева направо. Так же, как и человек видит простые примеры. Так как никаких скобок нет, то он воспринимает всё именно так. Видит, что есть переменна, видит два плюса. Всё. На три плюса никаких обработок нет. Потому берет выражение (а++) и обрабатывает его. Но остался еще +b. Потому результат (а++) суммируется с b. Не знаю как Вам ещё это объяснить. Более подробно может расскажут остальные. С удовольствием почитаю.

Регистрация: 27.06.2013
Сообщений: 21

ЦитатаСообщение от Тамика Посмотреть сообщение

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

Да, я тоже! Особенно интересно: как должен поступить компилятор согласно стандарта C. Или же это поведение оставлено на волю создателей компилятора и в стандарте не прописано?

Неэпический
17865 / 10631 / 2052
Регистрация: 27.09.2012
Сообщений: 26,731
Записей в блоге: 1

Цитата

Сообщение от Стефан К. Дьюхэрст «Скользкие места C++»

Добро пожаловать в мир «больших кусков». На одной из ранних стадий трансляции программы на C++ работает так называемый «лексический анализатор», задача которого, разбить входной поток на отдельные лексические единицы или лексемы. Встретив последовательность символов типа ->*, лексический анализатор может выделить три лексемы (-,> и *), две лексемы (-> и *) или одну лексему (->*), и все это будет разумно. Чтобы избежать неоднозначности, анализатор всегда выделяет самую длинную из возможных лексем: «максимальный кусок».

Регистрация: 27.06.2013
Сообщений: 21

Croessmah, это уже близко.
Но всё-таки как ни понимай +++: либо ++ +, либо + ++, всё равно получается один двухплюсовый кусок и один одноплюсовый. Ни в одном из двух вариантов куски не больше второго. Куски одинаковые, просто местами меняются. Может, имеется в виду, что «левосторонние» лексические анализаторы ищут большие куски слева?

Эксперт С++

2924 / 1274 / 114
Регистрация: 27.05.2008
Сообщений: 3,465

Лучший ответ

Сообщение было отмечено tolik89u как решение

Решение

Такое поведение компилятора жестко определено Стандартом: например, [C99, 6.4/4] или [C++11, 2.5/3].
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562

ЦитатаСообщение от tolik89u Посмотреть сообщение

Здравствуйте.
Как вычисляется выражение a+++b?
Как компилятор понимает, что означает +++: ++ + или + ++? Как он разбивает последовательность из трёх плюсов на два оператора? Есть понятия приоритета операций и ассоциативности, но они применяются уже к определённым операторам. А в данном случае как компилятор понимает, какой оператор использовался?

Согласно жадному алгоритму, в первый из двух операторов попадёт максимально возможное количество символов, получится

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

Добавлено через 52 секунды

ЦитатаСообщение от tolik89u Посмотреть сообщение

«Почему компилятор использовал тот или иной оператор?»

Потому что жадина.

Добавлено через 3 минуты

ЦитатаСообщение от Тамика Посмотреть сообщение

итая слева направо. Так же, как и человек видит простые примеры.
Нет. Человек в этом месте видит:
1.
, где +++ — один оператор.
2.

.
И выбирает, опираясь на знания языка (отбрасывая первые два варианта) и алгоритма трансляции, анализируя фрагмент за компилятор. Компилятор же сразу принимает третий вариант.

Добавлено через 4 минуты

ЦитатаСообщение от tolik89u Посмотреть сообщение

Но всё-таки как ни понимай +++: либо ++ +, либо + ++, всё равно получается один двухплюсовый кусок и один одноплюсовый. Ни в одном из двух вариантов куски не больше второго.

Не надо сравнивать сумму, или среднее кусков, важно, где больше первый кусок.
Котовчанин
942 / 482 / 200
Регистрация: 16.02.2010
Сообщений: 3,338
Записей в блоге: 37

ЦитатаСообщение от taras atavin Посмотреть сообщение

Нет. Человек в этом месте видит:

Я говорила о ПРОСТЫХ примерах и речь шла о направлении «чтения» выражения. Если я напишу а+б, разве Вы увидите в этом +аб или ба+ ? Нет. Увидите слева направо а+б. А потом уже описывала, что будет дальше.

Регистрация: 27.06.2013
Сообщений: 21

ЦитатаСообщение от CheshireCat Посмотреть сообщение

Такое поведение компилятора жестко определено Стандартом: например, [C99, 6.4/4] или [C++11, 2.5/3].

Спасибо! Чётко и по существу! Меня больше интересовал чистый C, поэтому я ограничился прочтением первой ссылки. Для всех интересующихся — вот что там сказано:

Цитата

Сообщение от C99, 6.4/4-6

4 If the input stream has been parsed into preprocessing tokens up to a given character, the
next preprocessing token is the longest sequence of characters that could constitute a
preprocessing token. There is one exception to this rule: header name preprocessing
tokens are recognized only within #include preprocessing directives and in
implementation-defined locations within #pragma directives. In such contexts, a
sequence of characters that could be either a header name or a string literal is recognized
as the former.
5 EXAMPLE 1 The program fragment 1Ex is parsed as a preprocessing number token (one that is not a
valid floating or integer constant token), even though a parse as the pair of preprocessing tokens 1 and Ex
might produce a valid expression (for example, if Ex were a macro defined as +1). Similarly, the program
fragment 1E1 is parsed as a preprocessing number (one that is a valid floating constant token), whether or
not E is a macro name.
6 EXAMPLE 2 The program fragment x+++++y is parsed as x ++ ++ + y, which violates a constraint on
increment operators, even though the parse x ++ + ++ y might yield a correct expression.

В частности, в параграфе 6 чётко прописано пояснение на примере, разъясняющем конкретно мой случай.
Ещё раз убеждаюсь в истинности одного изречения, с которым меня давно ознакомил один админ. В этом изречении сокрыта вековая мудрость программистов-предков:

Калькулятор комплексных чисел. Вычисление выражений с комплексными числами

Калькулятор комплексных чисел позволяет вычислять арифметические выражения, содержащие комплексные числа, знаки арифметических действий (+, -, *, /, ^), а также некоторые математические функции.

Калькулятор комплексных чисел

Скрыть клавиатуру
С решением
Тригонометрическая форма
Показательная форма
Десятичных знаков:

Как пользоваться калькулятором

  1. Введите в поле ввода выражение с комплексными числами
  2. Укажите, требуется ли вывод решения переключателем «С решением»
  3. Нажмите на кнопку «Построить»

Ввод комплексных чисел

комплексные числа можно вводить в следующих трёх форматах:

  • Только действительная часть: 2, 2.5, -6.7, 12.25
  • Только мнимая часть: i, -i, 2i, -5i, 2.16i, -12.5i
  • Действительная и мнимая части: 2+i, -5+15i, -7+2.5i, -6+i
  • Математические константы: π, e

Поддерживаемые операции и математические функции

  • Арифметические операции: +, -, *, /, ^
  • Получение абсолютного значения числа: abs
  • Базовые математические функции: exp, ln, sqrt
  • Получение действительной и мнимой частей: re, im
  • Тригонометрические функции: sin, cos, tg, ctg
  • Гиперболические функции: sh, ch, th, cth
  • Обратные тригонометрические функции: arcsin, arccos, arctg, arcctg
  • Обратные гиперболические функции: arsh, arch, arth, arcth

Примеры корректных выражений

  • (2+3i)*(5-7i)
  • sh(i)
  • (4+i) / (3 — 4i)
  • sqrt(2i)
  • (-3+4i)*2i / exp(2i + (15 — 8i)/4 — 3.75)

Комплексные числа

Комплексные числа — это числа вида x+iy , где x , y — вещественные числа, а i — мнимая единица (специальное число, квадрат которого равен -1, то есть i 2 = -1 ).
Так же, как и для вещественных чисел, для комплексных чисел определены операции сложения, разности, умножения и деления, однако комплексные числа нельзя сравнивать.

Примеры комплексных чисел

  • 4+3i — действительная часть = 4, мнимая = 3
  • -2+i — действительная часть = -2, мнимая = 1
  • i — действительная часть = 0, мнимая = 1
  • -i — действительная часть = 0, мнимая = -1
  • 10 — действительная часть = 10, мнимая = 0

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

Основными операциями, определёнными для комплексных чисел, являются сложение, разность, произведение и деление комплексных чисел. Операции для двух произвольных комплексных чисел (a + bi) и (c + di) определяются следующим образом:

  • сложение: (a + bi) + (c + di) = (a + c) + (b + d)i
  • вычитание: (a + bi) — (c + di) = (a — c) + (b — d)i
  • умножение: (a + bi) · (c + di) = ac + bci + adi + bdi 2 = (ac — bd) + (bc + ad)i
  • деление:

(a + bi)(c — di)

Примеры

Найти сумму чисел 5+7i и 5.5-2i :
Найдём отдельно суммы действительных частей и сумму мнимых частей: re = 5 + 5.5 = 10.5, im = 7 — 2 = 5.
Запишем их рядом, добавив к мнимой части i: 10.5 + 5i
Полученное число и будет ответом: 5+7i + 5.5-2i = 10.5 + 5i

Найти разность чисел 12-i и -2i :
Найдём отдельно разности действительных частей и разности мнимых частей: re = 12 — 0 = 12, im = -1 — (-2) = 1.
Запишем их рядом, добавив к мнимой части i: 12 + 1i
Полученное число и будет ответом: 12-i — (-2i) = 12 + i

Найти произведение чисел 2+3i и 5-7i :
Найдём по формуле действительную и мнимую части: re = 2·5 — 3·(-7) = 31, im = 3·5 + 2·(-7) = 1.
Запишем их рядом, добавив к мнимой части i: 31 + 1i
Полученное число и будет ответом: 2+3i * (5-7i) = 31 + i

Найти отношение чисел 75-50i и 3+4i :
Найдём по формуле действительную и мнимую части: re = (75·3 — 50·4) / 25 = 1, im = (-50·3 — 75·4) / 25 = -18.
Запишем их рядом, добавив к мнимой части i: 1 — 18i
Полученное число и будет ответом: 75-50i / (3+4i) = 1 — 18i

Другие действия над комплексными числами

Помимо базовых операций сложения, вычитания, умножения и деления комплексных чисел существуют также различные математические функции. Рассмотрим некоторые из них:

  • Получение действительной части числа: Re(z) = a
  • Получение мнимой части числа: Im(z) = b
  • Модуль числа: |z| = √(a 2 + b 2 )
  • Аргумент числа: arg z = arctg(b / a)
  • Экспонента: e z = e a ·cos(b) + i·e a ·sin(b)
  • Логарифм: Ln(z) = ln |z| + i·arg(z)
  • Тригонометрические функции: sin z, cos z, tg z, ctg z
  • Гиперболические функции: sh z, ch z, th z, cth z
  • Обратные тригонометрические функции: arcsin z, arccos z, arctg z, arcctg z
  • Обратные гиперболические функции: arsh z, arch z, arth z, arcth z

Примеры

Найти действительную и мнимую части числа z, а также его модуль, если z = 4 — 3i
Re(z) = Re(4 — 3i) = 4
Im(z) = Im(4 — 3i) = -3
|z| = √(4 2 + (-3) 2 ) = √25 = 5

Формы представления комплексных чисел

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

  • Алгебраическая форма — наиболее часто используемая форма комплексного числа, запись числа в виде суммы действительной и мнимой частей: x+iy , где x — действительная часть, а y — мнимая часть
  • Тригонометричкая форма — запись вида r·(cos φ + isin φ) , где r — модуль комплексного числа (r = |z|), а φ — аргумент этого числа (φ = arg(z))
  • Показательная форма — запись вида r·e iφ , где r — модуль комплексного числа (r = |z|), e — число Эйлера, а φ — аргумент комплексного числа (φ = arg(z))

Пример:

Переведите число 1+i в тригонометрическую и показательную формы:

  • Найдём радиус (модуль) комплексного числа r: r = √(1 2 + 1 2 ) = √2
  • Найдём аргумент числа: φ = arctan(

Неопределенное поведение в C++

Достаточно сложной темой для программистов на С++ является undefined behavior. Даже опытные разработчики зачастую не могут четко сформулировать причины его возникновения. Статья призвана внести чуть больше ясности в этот вопрос.

Статья является ПЕРЕВОДОМ нескольких статей и выдержек из Стандарта по данной теме.

Что такое «точки следования»?

Стандарте сказано:

Точки следования (sequence points)– такие точки в процессе выполнения программы, в которых все побочные эффекты уже выполненного кода закончили свое действие, а побочные эффекты кода, подлежащего исполнению, еще не начали действовать. (§1.9/7)

Побочные эффекты? А что такое «побочные эффекты»?

Побочный эффект (side effect) (согласно Стандарту) – результат доступа к volatile объекту, изменения объекта, вызова функции из библиотеки I/O или же вызова функции, включающей в себя какие-то из этих действий. Побочный эффект является изменением состояния среды выполнения.

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

int x = y++; // «y» тоже int

В дополнение к операции инициализации переменной «x» значение переменной «y» изменилось из-за побочного эффекта оператора ++.
Что ж, с этим понятно. Далее к точкам следования. Альтернативное определение понятия «точка следования» дано Стивом Саммитом (автор книг «Язык C в вопросах и ответах», блога «comp.lang.c»):
Точка следования – момент времени, когда «пыль улеглась», и все встреченные побочные эффекты гарантированно завершены и остались позади.

Какие точки следования описаны в Стандарте C++?

В стандарте описаны следующие точки следования:

    в конце вычисления полного выражения (§1.9/16). Под «полным выражением» (full-expression) подразумевается выражение, не являющееся подвыражением (subexpression) — частью другого выражения (прим: вычисление полного выражения может включать в себя вычисление подвыражения, лексически не являющегося его частью. Например, подвыражения, участвующие в вычислении аргумента по умолчанию, считаются частью выражения, которое вызвало функцию, а не выражения, определяющего этот аргумент).

int a = 5; // «;» - точка следования в данном контексте

1. a && b (§5.14)
2. a || b (§5.15)
3. a? b: c (§5.16)
4. a, b (§5.18)

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

Что такое «неопределенное поведение»?

Стандарт дает определение словосочетанию «неопределенное поведение» в §1.3.12:

Неопределенное поведение (undefined behavior)– поведение, которое может возникать в результате использования ошибочных программных конструкций или некорректных данных, на которые Международный Стандарт не налагает никаких требований. Неопределенное поведение также может возникать в ситуациях, не описанных в Стандарте явно.

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

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

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

Неуточняемое поведение (unspecified behavior) (согласно Стандарту) – поведение, для которого Стандарт предлагает два или более возможных вариантов и не налагает четких требований на то, какой из них должен быть выбран в определенной ситуации.

  • аргументы в вызове функции
  • операнды операторов (напр. +, -, =, *, /), за исключением:
    1. операторов бинарной логики (&& и ||)
    2. оператора условия (?:)
    3. оператора запятой.

    (прим.: за исключением именно тех операторов, которые содержат точку следования)

Поведение, определяемое реализацией (implementation-defined behavior) (согласно Стандарту) – поведение правильно сформированной программной конструкции с правильными данными, которое зависит от реализации (должно быть документировано для каждой реализации).

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

int x = 5, y = 6; int z = x++ + y++; // не уточнено, будет вычислен первым x++ или y++ 

Еще один пример:

 int Hello() < return printf("Hello"); >int World() < return printf("World !"); >int main() < int a = Hello() + World(); /**может вывести «Hello World!» или «World! Hello» ^ | Функции могут быть вызваны в любом порядке **/ return 0; >

В §5/4 Стандарт говорит:

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

Говоря чуть проще, переменную между двумя точками следования нельзя менять больше одного раза. В выражении следующая точка следования обычно располагается на заключающей точке с запятой, а предыдущая – в конце предшествующего оператора. Выражение так же может содержать промежуточные точки следования.
Исходя из вышесказанного, следующие выражения создают неопределенное поведение:

 i++ * ++i; // i = ++i; // ++i = 2; // i изменено более 1 раза i = ++i +1 // i = (i,++i,++i); // нет точки следования между правым `++i` и присвоением `i` (`i` изменяется более 1 раза между 2мя точками следования) 

Но в то же время:

 i = (i, ++i, 1) + 1; // определено i = (++i,i++,i) // определено int j = i; j = (++i, i++, j*i); // определено 

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

 std::printf("%d %d", i,++i); // неизвестно, что произойдет раньше – вычисление (++i) или доступ к нему.

Еще один пример:

 a[i] = i++ ; // либо a[++i] = i , либо a[i++] = ++i и т.д. 
Я слышал, что в C++0x нет никаких Точек Следования, это правда?

Да, это правда.
Понятие «точка следования» было заменено комитетом ISO C++ на уточненное и дополненное понятие Отношения Следования [ДО\ПОСЛЕ].

Что такое Отношение Следования[ДО]?

  • ассиметрично
  • транзитивно
  • возникает между парами вычислений и формирующее из них частично упорядоченное множество (partially ordered set)

Формально, это означает, что при двух данных выражениях А и B, если А [следует ДО] B, то выполнение А должно предшествовать выполнению В. Если же А не [следует ДО] В, тогда выполнение А и В является неупорядоченным (unsequenced) (выполнение неупорядоченных вычислений может пересекаться).
Вычисление A и В являются неопределенно упорядоченным (indeterminantly sequenced), когда либо А [следует ДО] В, либо В [следует ДО] А, но что именно – не уточнено. Неопределенно упорядоченные вычисления не могут пересекаться, но любое из них может выполнятся первым.

Что означает слово «вычисление» в контексте C++0x ?
  • подсчет (computation) значения (включая определение положения объекта в памяти для вычисления значения gvalue-выражения и получение значения по ссылке для вычисления prvalue-выражения)
  • инициирование побочных эффектов

Стандарт говорит нам (§1.9/14):

Каждый подсчет значения и побочный эффект, связанные с полным выражением, [следуют ДО] подсчета значения и побочного эффекта, связанных со следующим полным выражением, которое будет вычислено.

int x; x = 10; ++x; 

В данном примере подсчет значения и побочный эффект, связанный с выражением (++x), [следует ПОСЛЕ] подсчета значения и побочного эффекта (x=10).

Ведь между вещами, описанными выше, и неопределенным поведением должна быть какая-то связь, да?

Конечно, связь есть.

В §1.9/15 упоминается, что:

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

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

 int main() < int num = 19 ; num = (num > 3) ; > 

1) Вычисление операндов оператора «+» неупорядоченно.
2) Вычисление операндов операторов «>» неупорядоченно.

§1.9/15 подсчет значения операндов конкретного оператора [следует ДО] подсчета значения результата работы оператора.

Это означает, что в выражении x + y подсчет значений «х» и «у» [следует ДО] подсчета x+y.

Теперь к более важному:

  • возникновению другого побочного эффекта этого же объекта
  • подсчету значения с использованием значения этого объект
 f(i = -1, i = -1);

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

Предположим, что компилятор решил, что оптимальнее всего присвоить «-1» будет обнулив переменную и сделав ее декремент.
Инструкции могут сформироваться так (команды условны):

clear i decr i clear i decr i
clear i clear i decr i decr i

после чего в i будет хранится значение -2.

При вызове функции каждый подсчет значения и побочный эффект, связанный с выражением аргумента этой функции, или с выражением, вызывающим функцию, [следует ДО] выполнения любого выражения или оператора в теле вызываемой функции.
Подсчет значения и побочные эффекты, связанные с разными аргументами, неупорядоченны.

Поток выполнения программы

Оперируя терминами, расшифрованными ранее, поток выполнения программы можно представить графически. В следующих далее диаграммах обозначим вычисление выражения (или подвыражения) как E(x), точку следования — %, побочный эффект «k» для объекта «e» обозначим S(k,e). Если для вычисления необходимо считать значение из именованного объекта (пусть «x» — имя), вычисление будем обозначать V(x), в остальных случаях – так же, как договаривались ранее, E(x). Побочные эффекты запишем справа и слева от выражений. Граница между двумя выражениями обозначает, что верхнее выражение вычисляется до нижнего выражения (зачастую потому что нижнее выражение зависит от prvalue или lvalue верхнего выражения).
Для двух выражений i++; i++; диаграмма будет иметь вид:

E(i++) -> < S(increment, i) >| % | E(i++) -> < S(increment, i) >| % 

Как видите, в данном случае мы имеем две точки следования, одна из которых разделяет два изменения «i».
Вызовы функций также представляют интерес, несмотря на то, что диаграмму для них мы опустим:

 int c = 0; int d = 0; void f(int a, int b) < assert((a == c - 1) && (b == d - 1)); >int main()

Этот код корректный, потому что к тому времени, как начнет выполняться тело функции f, все побочные эффекты, порожденные вычислением аргументов, гарантированно закончатся: «с» и «d» будут увеличены на 1.
Теперь рассмотрим выражение i++ * j++;

 < S(increment, i) >  < S(increment, j) >\ / +--+--+ | E(i++ * j++) | % 

Откуда же появилось две ветки? Напомним, что точки следования завершают вычисления, проводившиеся ДО их наступления. Все подвыражения умножения вычисляются до самого умножения, больше в этом выражении нет точки следования, следовательно, нам нужно принять во внимание теоретическую «параллельность» вычисления операндов, чтобы предположить, где может произойти конкурентное изменение одного и того же объекта. Говоря более формально, эти две ветви неупорядочены. Точки следования – это отношение, которое упорядочивает некоторые вычисления и не упорядочивает другие. Т.о. точки следования, как и говорилось выше, являются частичным упорядочиванием (partial order).

Конфликтующие побочные эффекты.

Чтобы обеспечить компилятору свободу в генерации и оптимизации машинного кода, в случаях, подобных рассмотренному выше умножению, не устанавливается порядок вычисления подвыражений и не разделяются побочные эффекты, порожденные ими (за исключением описанных ранее случаев).
Это может вести к конфликтам, поэтому Стандарт называет неопределенным поведение программы, если она пытается модифицировать один и тот же объект без участия точек следования. Это относится к скалярным объектам, потому что остальные объекты являются либо неизменяемыми (array) или попросту не подпадают под это правило (class objects). Неопределенное поведение также возникает, если в выражении присутствуют обращение к предыдущему значению объекта и его модификация, как например в i * i++

// Ведет к неопределенному поведению! // Не факт, что из левого 'i' будет считано «новое» значение: V(i) E(i++) -> < S(increment, i) >) \ / +---+---+ | E(i * i++) | % 

В качестве исключения позволено считывать значение объекта, если оно необходимо для подсчета нового значения. Пример контекста: i = i+1

 V(i) E(1) \ / +---+---+ | E(i) E(i + 1) \ / +-------+-------+ | E(i = i + 1) -> < S(assign, i) >| % 

Здесь мы видим обращение к «i» в правой части; после вычисления обеих частей совершается присваивание. Т.о. побочный эффект и обращение к «i» происходят, не пересекая точку следования, но обращались к «i» мы только для определения хранимого значения, поэтому разногласий не будет.
Иногда, значение считывается после модификации. Для случая
a = (b = 0);
справедливо, что происходит запись в «b», а потом чтение из «b» без пересечения точки следования. Тем не менее, это нормально, потому что считывается уже новое значение «b», а обращения к старому не происходит. В этом случае побочные эффекты присвоения «b» закончат свое действие не только до следующей точки следования, но и перед чтением значения «b», требуемого для присвоения «а». Стандарт явно говорит: «результатом операции присваивания является значение, хранимое в левом операнде, после того, как присваивание выполнено (результат — lvalue)». Почему не используется понятие точки следования? Потому что это понятие содержит ненужное в данной ситуации требование, чтобы все побочные эффекты левого и правого операнда были завершены, вместо того чтобы рассматривать только побочные эффекты присвоения, возвращающего lvalue, с помощью которого происходит считывание.

  • Актуальная версия стандарта
  • stackoverflow.com/questions/4176328/undefined-behavior-and-sequence-points?lq=1
  • stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior
  • undefined behavior
  • sequence points

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

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