rg45:
R>[Upd] R>Да, но как в таком случае понимать пример из 1.9/15?
R>
R>
R>void f(int, int);
R>void g(int i, int* v) {
R>i = v[i++]; // the behavior is undefined
R>i = 7, i++, i++; // i becomes 9
R>i = i++ + 1; // the behavior is undefined
R>i = i + 1; // the value of i is incremented
R>f(i = -1, i = -1); // the behavior is undefined
R>}
R> R>В обоих выделенных случаях, согласно 5.17/1, присваивание выполняется после модификации (инкремента)
До C++17 эти две модификации не были упорядочены между собой, т.к. здесь инкремент — постфиксный, а у него модификация следует за вычислением значения (у префиксного же вычисление значения следует за модификацией, что в рассмотренном ранее примере позволяет выстроить упорядоченную цепочку модификация -> вычисление значения -> модификация).
Здравствуйте, RussianFellow, Вы писали:
RF>Мне там не всё понятно. В частности, мне не понятно, что такое левоассоциативность и правоассоциативность операторов в C++.
Это как оно расставит скобки в цепочке одинаковых (или равноприоритетных) операторов, если скобок нет.
a+b+c интерпретируется (a+b)+c, а не a+(b+c) (для большинства случаев пофиг)
a-b-c — как (a-b)-c, а не a-(b-c) (а вот тут уже не пофиг, результат совсем другой)
это всё были левоассоциативные.
А вот пример правоассоциативного — возведение в степень — почти везде
в Фортране a**b**c понимается как a**(b**c), а не как (a**b)**c, потому что последнее равно a**(b*c), и смысла в такой ассоциативности — никакого.
В Си a=b=c понимается не как (a=b)=c, а как a=(b=c). Первый вариант не имеет смысла. Второй — имеет.
RF>Не могли бы Вы, уважаемые коллеги, объяснить мне получше, что это такое? А заодно и рассказать понятно про приоритет операторов в C++. RF>Желательно также привести сложные выражения с операторами и объяснить, что они означают.
А что они могут означать?
RF>Я сейчас читаю Страуструпа, но мне хотелось бы прочитать подробно, понятно, популярно про приоритет операторов в C++.
Почему не начать с более простой книги? Страуструп явно писал, что "не хочет оскорблять профессиональных программистов подробным разжёвыванием".
Здравствуйте, RussianFellow, Вы писали:
RF>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
лучше все равно всегда писать со скобками за редким-редким исключением.
Здравствуйте, RussianFellow, Вы писали:
RF>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
Открою секрет. Все помнят, что умножение имеет приоритет перед сложением, но более сложных правил не помнит никто. Поэтому все пишут сложные выражения со скобками.
rg45:
R>Если только все используетые операторы не являются перегруженными, то, согласно пункту 1.9/15 стандарта С++11, такое выражение порождает неопределенное поведение, поскольку скалярный объект, адресуемый указателем p1, модифицируется дважды посредством неупорядоченных побочных эффектов:
R>
R>If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
Если p1 и p2 — это обычные указатели, указывающие на разные неперекрывающиеся объекты скалярного типа, отличного от bool, то по правилам C++11 из-за множественной модификации undefined behavior тут возникнуть не может. Модификация результата ++*p1 оператором = выполняется после value computation для ++*p1, а это value computation выполняется после модификации результата *p1 оператором ++. В C++17 ввели ещё более строгую упорядоченность, и там даже такой вариант
++*p = ++*p;
не страшен возникновением undefined behavior за счёт множественной модификации (оно может возникнуть только по другим причинам).
Здравствуйте, N. I., Вы писали:
NI>Эти две модификации не упорядочены между собой, т.к. здесь инкремент — постфиксный, а у него модификация следует за вычислением значения (у префиксного же вычисление значения следует за модификацией, что в рассмотренном ранее примере позволяет выстроить упорядоченную цепочку модификация -> вычисление значения -> модификация).
правильно ли я понимаю, что в этом коде нет UB?
void g(int i, int* v) {
i = v[++i];
i = ++i + 1;
}
Здравствуйте, RussianFellow, Вы писали:
RF>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
Всегда лучше писать со скобками, иначе потом или читаемость будет хреновая или же закрадется бага, которую будешь искать пол дня.
SkyKnight:
RF>>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок? SK>Всегда лучше писать со скобками, иначе потом или читаемость будет хреновая
Зависит от того, кто читает. Мне, например, удобнее читать
if (*first < 0xDC00 && first + 1 != ending && 0xDC00 <= first[1] && first[1] < 0xF000)
Мне там не всё понятно. В частности, мне не понятно, что такое левоассоциативность и правоассоциативность операторов в C++.
Не могли бы Вы, уважаемые коллеги, объяснить мне получше, что это такое? А заодно и рассказать понятно про приоритет операторов в C++.
Желательно также привести сложные выражения с операторами и объяснить, что они означают.
Я сейчас читаю Страуструпа, но мне хотелось бы прочитать подробно, понятно, популярно про приоритет операторов в C++.
Здравствуйте, RussianFellow, Вы писали:
RF>Ну, например, что означают выражения:
(++(*p1)) = (++(*p2));
(*(++p1)) = (*(++p2);
(*(p1++)) = (*(p2++));
RF>И чем выражение RF>
RF>for (i=0; i<10; i++);
RF>
RF> отличается от выражения RF>
RF>for (i=0; i<10; ++i);
RF>
RF>?
С точки зрения наблюдаемого поведения — ничем. Если иначе — значит перегрузку операторов ++, для того чем является i, писал диверсант.
Для сложных типов данных, например итераторы контейнеров, префиксный оператор (++i) написать проще чем суфиксный (i++), и обычно он работает быстрее (ненужна дополнительная переменная).
И этот пример уже не имеет отношения к приоритетам операторов.
Здравствуйте, RussianFellow, Вы писали:
RF>Ну, например, что означают выражения:
RF>
RF>++*p1 = ++*p2;
RF>
Разыменование двух указателей с последующим инкрементом адресуемых объектов и присваиванием.
Если только все используетые операторы не являются перегруженными, то, согласно пункту 1.9/15 стандарта С++11, такое выражение порождает неопределенное поведение, поскольку скалярный объект, адресуемый указателем p1, модифицируется дважды посредством неупорядоченных побочных эффектов:
If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
В C++03 это поведение регулировалось таким понятием как "точки следования". Начиная с C++11 точки следования были заменены уточненными понятиями, но принцип сохранился.
--
Не можешь достичь желаемого — пожелай достигнутого.
а если хочется чего-то на пальцах, то набираете в гугле что-нить наподобие "operator precedence associativity" с фильтром по картинкам или видео. и находите, например, это: 05 operator associativity and precedence in c part 1
Здравствуйте, N. I., Вы писали:
NI>Если p1 и p2 — это обычные указатели, указывающие на разные неперекрывающиеся объекты скалярного типа, отличного от bool, то по правилам C++11 из-за множественной модификации undefined behavior тут возникнуть не может. Модификация результата ++*p1 оператором = выполняется после value computation для ++*p1, а это value computation выполняется после модификации результата *p1 оператором ++.
Да, действительно, это, оказывается, является свойством оператора присваивания:
5.17/1
In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.
[Upd]
Да, но как в таком случае понимать пример из 1.9/15?
void f(int, int);
void g(int i, int* v) {
i = v[i++]; // the behavior is undefined
i = 7, i++, i++; // i becomes 9i = i++ + 1; // the behavior is undefined
i = i + 1; // the value of i is incremented
f(i = -1, i = -1); // the behavior is undefined
}
В обоих выделенных случаях, согласно 5.17/1, присваивание выполняется после модификации (инкремента), происходящего при вычислении правой части. Почему же возникает неопределенное поведение?
--
Не можешь достичь желаемого — пожелай достигнутого.
uzhas:
U>правильно ли я понимаю, что в этом коде нет UB? U>
U>void g(int i, int* v) {
U> i = v[++i];
U> i = ++i + 1;
U>}
U>
Если эту функцию вызывают с подходящими аргументами (так, что в результате не получается разыменование нулевого указателя, выход за границы массива, вычисление значения неинициализированного объекта, целочисленное переполнение и т.д.), то в случае с C++11/14/17 undefined behavior тут не будет.
В соответствии с
If x is not of type bool, the expression ++x is equivalent to x+=1
поведение в этом примере должно быть такое же, как у
void g(int i, int* v) {
i = v[i += 1];
i = (i += 1) + 1;
}
Дальше на основании
In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.
и
The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
можно выстроить цепочки
+= assignment → += value computation → [] value computation → = assignment += assignment → += value computation → + value computation → = assignment
R>>void f(int, int);
R>>void g(int i, int* v) {
R>>i = v[i++]; // the behavior is undefined
R>>i = 7, i++, i++; // i becomes 9
R>>i = i++ + 1; // the behavior is undefined
R>>i = i + 1; // the value of i is incremented
R>>f(i = -1, i = -1); // the behavior is undefined
R>>}
R>>
NI>До C++17 эти две модификации не были упорядочены между собой, т.к. здесь инкремент — постфиксный, а у него модификация следует за вычислением значения...
Что-то я как-то медленно соображать стал. Выходит, что в случае использования постинкремента, в отличие от преинкремента, применение побочного эфффекта не является необходимым для вычисления правой части оператора присваивания, таким образом инкремент и присваивание оказываются неупорядоченными друг с другом. Правильно?
В этом контексте нелишним будет упомянуть правила для постфиксных инкремента/декремента:
The value computation of the ++ expression is sequenced before the modification of the operand object.
Таким образом, при использовании постинкремента в правой части, мы знаем только то, что и присваивание и инкремент произойдут после вычисления значения подвыражения преинкремента, но в каком порядке — неизвестно.
--
Не можешь достичь желаемого — пожелай достигнутого.
rg45:
R>Таким образом, при использовании постинкремента в правой части, мы знаем только, и присваивание и инкремент произойдут после value computation, но в каком порядке — неизвестно.
Примерно так, но тут следует уточнить, что порядок не неизвестен, его вообще не существует. Наличие неизвестного порядка и отсутствие какого-либо порядка — это две разные ситуации. Две unsequenced модификации одного скалярного объекта приводят к undefined behavior, а две indeterminately sequenced модификации одного скалярного объекта — нет. В данном случае у нас именно unsequenced modifications.
В C++17 строгий порядок в таком примере будет обеспечен за счёт нового правила для операторов присваивания:
The right operand is sequenced before the left operand.
Здравствуйте, Andrew.W Worobow, Вы писали:
RF>>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
AWW>лучше все равно всегда писать со скобками за редким-редким исключением.
Да и вообще писать так, чтобы скобки не требовались.
Вчера несколько часов отлаживал ошибку после неудачной копипасты выражения с "i++".
Здравствуйте, Pzz, Вы писали:
Pzz>Открою секрет. Все помнят, что умножение имеет приоритет перед сложением, но более сложных правил не помнит никто. Поэтому все пишут сложные выражения со скобками.
Ещё полезно помнить, что с переопределёнными << для потоков не всё так просто.