Отношение sequenced-before и относящиеся к нему правила представляют собой «приведение в порядок» предыдущих правил для точек последовательности, определенных согласованным образом с другими отношениями модели памяти, такими как происходит до того, как и синхронизируются с , чтобы можно было точно указать, какие операции и эффекты видны при каких обстоятельствах.
Последствия правил неизменны для простого однопоточного кода.
Давайте начнем с ваших примеров:
1. i = ++i;
Если i
является встроенным типом, таким как int
, то вызовы функций не задействованы, все является встроенным оператором. Таким образом, происходит 4 вещи:
(a) вычисление значения из ++i
, что составляет исходное значение i +1
(b) побочный эффект из ++i
, который сохраняет исходное значение-я +1
обратно в i
(c) вычисление значения присвоения, которое является просто сохраненным значением, в этом случае результат вычисления значения из ++i
(d) побочный эффект присвоения, который сохраняет новое значение в i
Все эти вещи секвенированы перед следующим полным выражением. (т. е. все они завершаются последней точкой с запятой утверждения)
Поскольку ++i
эквивалентно i+=1
, побочный эффект сохранения значения составляет секвенирование до вычисление значения ++i
, так что (b) секвенировано до (a).
значение вычисления обоих операндов присваивания равно секвенировано до значение вычислено самого назначения, и это в свою очередь sequenced-before побочный эффект сохранения значения. Поэтому (a) секвенируется перед (c), а (c) секвенируется до (d).
Таким образом, у нас есть (b) -> (a) -> (c) -> (d), и, таким образом, это нормально в соответствии с новыми правилами, тогда как в C ++ 98 это было не так.
Если i
было class
, то выражение будет i.operator=(i.operator++())
или i.operator=(operator++(i))
, и все эффекты вызова operator++
sequenced-before звонок на operator=
.
2. a[++i] = i;
Если a
является типом массива, а i
является int
, то снова выражение имеет несколько частей:
(a) Расчет значения из i
(b) Вычисление значения из ++i
(c) Побочный эффект из ++i
, который сохраняет новое значение обратно в i
(d) Вычисление значения для a[++i]
, которое возвращает значение для элемента a
, проиндексированного вычислением значения из ++i
(e) вычисление значения присвоения, которое является просто сохраненным значением, в данном случае результат вычисления значения из i
(f) Побочный эффект присваивания *1138*, который сохраняет новое значение в элементе массива a[++i]
Опять же, все эти вещи упорядочены до следующего полного выражения. (т. е. все они завершаются последней точкой с запятой утверждения)
Опять же, поскольку ++i
эквивалентно i+=1
, побочный эффект сохранения значения равен sequenced-before вычисление значения of ++i
, поэтому (c) является упорядоченным до (b).
значение вычисления индекса массива ++i
является * последовательным до 'вычисления значения выбора элемента, поэтому (b) равно последовательным до (д).
значение вычисления обоих операндов присваивания равно последовательность до значение значения самого присвоения, и это в свою очередь sequenced-before побочный эффект сохранения значения. Поэтому (a) и (d) секвенируются до (e), а (e) секвенируются до (f).
Следовательно, у нас есть две последовательности: (a) -> (d) -> (e) -> (f) и (c) -> (b) -> (d) -> (e) -> (f ).
К сожалению, нет порядка между (a) и (c). Таким образом, побочный эффект , который сохраняется в i
, является непоследовательным относительно вычисления значения для i
, и код демонстрирует неопределенное поведение . Это опять-таки указано в 1.9p15 стандарта C ++ 11.
Как и выше, если i
относится к типу класса, то все в порядке, потому что операторы становятся вызовами функций, которые навязывают последовательность.
Правила
Правила относительно просты:
Значения аргументов встроенного оператора: секвенировано до значение самого оператора.
Побочные эффекты встроенного оператора присваивания или оператора преинкремента представляют собой последовательность до вычисление значения результата.
Вычисление значения любого другого встроенного оператора является секвенированным до побочных эффектов этого оператора.
Вычисление значения и побочных эффектов левой части встроенного оператора запятой sequenced- до вычисления значения и побочных эффектов с правой стороны.
Все значения вычисления и побочные эффекты полного выражения sequenced-before следующее полное выражение.
Вычисление значения и побочных эффектов аргументов вызова функции упорядочены перед первым полным выражением в функции.
Вычисление значения и побочные эффекты всего внутри функция секвенируется перед значением вычисление результата.
Для любых двух вызовов функций в полном выражении либо вычисление значения результата одного является последовательным до вызова другого, либо наоборот. Если никакое другое правило не определяет порядок, компилятор может выбрать.
Таким образом, в a()+b()
либо a()
равен секвенировано до b()
, либо b()
равно секвенировано-до a()
, но правила не существует указать какой.
Если есть два побочных эффекта , которые изменяют одну и ту же переменную, и ни один из них не sequenced-before другой, код имеет неопределенное поведение.
Если есть побочный эффект , который изменяет переменную, и вычисление значения , которое читает эту переменную, и ни один из них не является sequenced-before другой код имеет неопределенное поведение.