Я просто собираюсь добавить подробную ссылку на правило as-if и ключевое слово volatile . (Внизу этих страниц следуйте пунктам «См. Также» и «Ссылки», чтобы вернуться к исходным спецификациям, но я считаю, что cppreference.com намного легче читать / понимать.)
В частности, я хочу, чтобы вы прочитали этот раздел
volatile объект - объект, тип которого является изменчивым, или подобъект изменчивого объекта, или изменяемый подобъект объекта const-volatile. Каждый доступ (операция чтения или записи, вызов функции-члена и т. Д.), Выполняемый через выражение glvalue с типом с переменной volatile, рассматривается как видимый побочный эффект в целях оптимизации (то есть в рамках одного потока выполнения volatile доступы не могут быть оптимизированы или переупорядочены с другим видимым побочным эффектом, который упорядочен - до или после энергозависимого доступа. Это делает энергозависимые объекты подходящими для связи с обработчиком сигнала, но не с другим потоком выполнения, см. std :: memory_order ). Любая попытка обратиться к изменчивому объекту через энергонезависимое значение glvalue (например, через ссылку или указатель на энергонезависимый тип) приводит к неопределенному поведению.
Таким образом, ключевое слово volatile специально предназначено для отключения оптимизации компилятора для glvalues . Единственное, на что здесь может повлиять ключевое слово volatile, так это то return x
, что компилятор может делать все, что захочет, с остальной частью функции.
Насколько компилятор может оптимизировать возврат, зависит от того, насколько компилятору разрешено оптимизировать доступ к x в этом случае (поскольку он ничего не переупорядочивает и, строго говоря, не удаляет возвращаемое выражение. , но он читает и записывает в стек, который должен быть в состоянии упростить.) Итак, когда я это читал, это серая область в том, насколько компилятор может оптимизировать, и это легко может быть аргументировано обоими способами.
Боковое примечание: в этих случаях всегда предполагайте, что компилятор будет делать противоположное тому, что вы хотели / требовали. Вам следует либо отключить оптимизацию (по крайней мере, для этого модуля), либо попытаться найти более определенное поведение для того, что вы хотите. (Вот почему так важно модульное тестирование). Если вы считаете, что это дефект, вы должны сообщить об этом разработчикам C ++.
Все это все еще очень трудно читать, поэтому постарайтесь включить то, что я считаю актуальным, чтобы вы могли прочитать это сами.
glvalue Выражение glvalue - это либо lvalue, либо xvalue.
Свойства:
Glvalue может быть неявно преобразовано в prvalue с помощью неявного преобразования lvalue-to-rvalue, массива в указатель или функции в указатель. Значение glvalue может быть полиморфным: динамический тип идентифицируемого объекта не обязательно является статическим типом выражения. Glvalue может иметь неполный тип, если это разрешено выражением.
xvalue Следующие выражения являются выражениями xvalue:
вызов функции или перегруженное операторное выражение, возвращаемый тип которого - ссылка rvalue на объект, например std :: move (x); a [n], встроенное выражение индекса, где один операнд является массивом rvalue; am, член объектного выражения, где a - значение r, а m - нестатический элемент данных не ссылочного типа; a. * mp, указатель на член объектного выражения, где a - rvalue, а mp - указатель на член данных; а? b: c, тернарное условное выражение для некоторых b и c (подробности см. в определении); выражение приведения к rvalue, ссылка на тип объекта, например static_cast (x); любое выражение, обозначающее временный объект после временной материализации. (начиная с C ++ 17) Свойства:
То же, что и rvalue (ниже). То же, что и glvalue (ниже). В частности, как и все rvalues, xvalues привязываются к ссылкам rvalue, и, как все glvalue, xvalue могут быть полиморфными, а значения x, не относящиеся к классу, могут быть cv-квалифицированными.
lvalue Следующие выражения являются выражениями lvalue:
имя переменной, функции или члена данных, независимо от типа, например std :: cin или std :: endl. Даже если тип переменной является ссылкой rvalue, выражение, состоящее из ее имени, является выражением lvalue; вызов функции или перегруженное операторное выражение, возвращаемый тип которого - ссылка lvalue, например std :: getline (std :: cin, str), std :: cout << 1, str1 = str2 или ++ it; a = b, a + = b, a% = b и все другие встроенные выражения присваивания и составные выражения присваивания; ++ a и --a, встроенные выражения пре-инкремента и пре-декремента; * p, встроенное косвенное выражение; a [n] и p [n], встроенные выражения нижнего индекса, кроме тех случаев, когда a является массивом rvalue (начиная с C ++ 11); am, член объектного выражения, за исключением случаев, когда m является перечислителем членов или нестатической функцией-членом, или где a - значение r, а m - нестатический элемент данных не ссылочного типа; p-> m, встроенный член выражения указателя, за исключением тех случаев, когда m является перечислителем членов или нестатической функцией-членом; a. * mp, указатель на член объектного выражения, где a - lvalue, а mp - указатель на член данных; p -> * mp, встроенный указатель на член выражения указателя, где mp - указатель на член данных; a, b, встроенное выражение-запятая, где b - lvalue; а? b: c, тернарное условное выражение для некоторых b и c (например, когда оба являются lvalue одного типа, но подробности см. в определении); строковый литерал, например «Hello, world!»; выражение приведения к ссылочному типу lvalue, например static_cast (x); вызов функции или перегруженное выражение оператора, чей возвращаемый тип - ссылка rvalue на функцию; выражение приведения к rvalue, ссылка на тип функции, например static_cast (x). (начиная с C ++ 11) Свойства:
То же, что и glvalue (ниже). Можно взять адрес lvalue: & ++ i 1
и & std :: endl - допустимые выражения. Изменяемое lvalue может использоваться как левый операнд встроенных операторов присваивания и составного присваивания. Lvalue может использоваться для инициализации ссылки lvalue; это связывает новое имя с объектом, идентифицированным выражением.
как если бы правило
Компилятору C ++ разрешено вносить любые изменения в программу, пока выполняется следующее:
1) В каждой точке последовательности значения всех изменчивых объектов стабильны (предыдущие оценки завершены, новые оценки не начаты) (до C ++ 11) 1) Доступ (чтение и запись) к изменчивым объектам происходит строго в соответствии с семантикой выражений, в которых они встречаются. В частности, они не переупорядочиваются по отношению к другим изменчивым доступам в том же потоке. (начиная с C ++ 11) 2) При завершении программы данные, записанные в файлы, точно такие же, как если бы программа выполнялась в том виде, в котором она была написана. 3) Текст приглашения, который отправляется на интерактивные устройства, будет показан до того, как программа ожидает ввода. 4) Если прагма ISO C #pragma STDC FENV_ACCESS поддерживается и имеет значение ON,
Если вы хотите прочитать спецификации, я считаю, что это те, которые вам нужно прочитать
Рекомендации
Стандарт C11 (ISO / IEC 9899: 2011): 6.7.3 Квалификаторы типа (стр. 121-123)
Стандарт C99 (ISO / IEC 9899: 1999): 6.7.3 Определители типа (стр: 108-110)
Стандарт C89 / C90 (ISO / IEC 9899: 1990): 3.5.3 Квалификаторы типа