Отношение sequenced-before и связанные с ним правила представляют собой упорядочение предшествующих правил для точек следования, определенных согласованным образом с другими отношениями модели памяти, такими как происходит-before< /em> и синхронизируется с, чтобы можно было точно указать, какие операции и эффекты видны и при каких обстоятельствах.
Последствия правил неизменны для простого однопоточного кода.
Начнем с ваших примеров:
1. i = ++i;
Если i
является встроенным типом, таким как int
, то вызовы функций не задействованы, все является встроенным оператором. Таким образом, происходят 4 вещи:
(a) вычисление значения ++i
, которое равно исходному-значению-i +1
(b) побочный эффект ++i
, который сохраняет исходное-значение-i +1
обратно в i
(c) вычисление значения присвоения, которое представляет собой просто сохраненное значение, в данном случае результат вычисления значения ++i
(d) побочный эффект присваивания, который сохраняет новое значение в i
Все эти вещи последовательны-перед следующим полным выражением. (т.е. все они завершаются последней точкой с запятой оператора)
Поскольку ++i
эквивалентно i+=1
, побочным эффектом сохранения значения является упорядочивание вычисления значения ++i
, поэтому (b) последовательно-до (а).
вычисление значения обоих операндов присваивания выполняется в последовательности перед вычислением значения самого присваивания, а это, в свою очередь, в последовательности перед побочным эффектом сохранения значения. Следовательно, (a) последовательно расположено перед (c), а (c) последовательно-перед (d).
Таким образом, у нас есть (b) -> (a) -> (c) -> (d), и, таким образом, это нормально по новым правилам, тогда как в C++98 это было неправильно.
Если бы i
было class
, тогда выражение было бы i.operator=(i.operator++())
или i.operator=(operator++(i))
, а все эффекты вызова operator++
последовательны перед вызовом operator=
.
2. a[++i] = i;
Если a
— это тип массива, а i
— это int
, то выражение снова состоит из нескольких частей:
(a) вычисление значения i
(b) вычисление значения ++i
(c) побочный эффект ++i
, который сохраняет новое значение обратно в i
(d) вычисление значения для a[++i]
, которое возвращает lvalue для элемента a
, индексированного вычислением значения для ++i
.
(e) вычисление значения присваивания, которое представляет собой просто сохраненное значение, в данном случае результат вычисления значения i
(f) побочный эффект присваивания, при котором новое значение сохраняется в элементе массива a[++i]
.
Опять же, все эти вещи последовательны-перед следующим полным выражением. (т.е. все они завершаются последней точкой с запятой оператора)
Опять же, поскольку ++i
эквивалентно i+=1
, побочным эффектом сохранения значения является упорядочивание до вычисления значения ++i
, поэтому (c ) находится в последовательности-до (b).
вычисление значения индекса массива ++i
выполняется *sequenced-before` вычисление значения выбора элемента, поэтому (b) выполняется sequence-before (г).
вычисление значения обоих операндов присваивания выполняется в последовательности перед вычислением значения самого присваивания, а это, в свою очередь, в последовательности перед побочным эффектом сохранения значения. Следовательно, (a) и (d) последовательны до (e), а (e) последовательны до (f).
Таким образом, мы имеем две последовательности: (a) -> (d) -> (e) -> (f) и (c) -> (b) -> (d) -> (e) -> (f).
К сожалению, порядок между (а) и (с) отсутствует. Таким образом, побочный эффект, который сохраняется в i
, является неупорядоченным по отношению к вычислению значения на i
, и код демонстрирует неопределенное поведение< /сильный>. Это снова дается 1.9p15 стандарта C++11.
Как и выше, если i
относится к классу, то все в порядке, потому что операторы становятся вызовами функций, что налагает последовательность.
Правила
Правила относительно просты:
Вычисление значения аргументов встроенного оператора выполняется в последовательности перед вычислением значения самого оператора.
Побочные эффекты встроенного оператора присваивания или оператора преинкремента упорядочиваются до вычисления значения результата.
вычисление значения любого другого встроенного оператора выполняется в последовательности перед побочными эффектами этого оператора.
вычисление значения и побочные эффекты левой части встроенного оператора запятой упорядочиваются-перед em> вычисление значения и побочные эффекты правой части.
Все вычисления значений и побочные эффекты полного выражения упорядочиваются перед следующим полным выражением.
вычисление значения и побочные эффекты аргументов вызова функции упорядочиваются перед первым полным выражением в функции.
вычисление значения и побочные эффекты всего, внутри функции, упорядочиваются — до вычисления значения< /em> результата.
Для любых двух вызовов функций в полном выражении либо вычисление значения результата одной выполняется в последовательности — до вызова другой, либо наоборот. Если никакое другое правило не определяет порядок, компилятор может выбрать.
Таким образом, в a()+b()
либо a()
находится последовательно-перед b()
, либо b()
последовательно-перед a()
, но нет правила, чтобы указать, какое именно.
Если есть два побочных эффекта, которые изменяют одну и ту же переменную, и ни один из них не последовательно расположен перед другим, код имеет неопределенное поведение.
Если есть побочный эффект, который изменяет переменную, и вычисление значения, которое считывает эту переменную, и ни один из них не упорядочивается-перед другим, код имеет неопределенное поведение.
person
Anthony Williams
schedule
06.03.2012