Solo voy a agregar una referencia detallada para la regla como si y la palabra clave volátil . (En la parte inferior de estas páginas, siga "ver también" y "Referencias" para rastrear las especificaciones originales, pero creo que cppreference.com es mucho más fácil de leer / comprender).
En particular, quiero que leas esta sección
objeto volátil: un objeto cuyo tipo es volátil calificado, o un subobjeto de un objeto volátil, o un subobjeto mutable de un objeto constante-volátil. Cada acceso (operación de lectura o escritura, llamada a función miembro, etc.) realizado a través de una expresión glvalue de tipo calificado volátil se trata como un efecto secundario visible para fines de optimización (es decir, dentro de un único hilo de ejecución, volátil los accesos no se pueden optimizar o reordenar con otro efecto secundario visible que se secuencia antes o después del acceso volátil. Esto hace que los objetos volátiles sean adecuados para la comunicación con un manejador de señales, pero no con otro hilo de ejecución, ver std :: memory_order ). Cualquier intento de hacer referencia a un objeto volátil a través de un glvalue no volátil (por ejemplo, a través de una referencia o un puntero a un tipo no volátil) da como resultado un comportamiento indefinido.
Entonces, la palabra clave volátil se trata específicamente de deshabilitar la optimización del compilador en glvalues . Lo único que aquí puede afectar la palabra clave volátil es posiblemente que return x
el compilador puede hacer lo que quiera con el resto de la función.
Cuánto puede optimizar el compilador el retorno depende de cuánto se le permita al compilador optimizar el acceso de x en este caso (ya que no está reordenando nada, y estrictamente hablando, no está eliminando la expresión de retorno. Existe el acceso , pero está leyendo y escribiendo en la pila, que debería poder simplificarse). Así que mientras lo leo, esta es un área gris en cuanto a cuánto se le permite optimizar al compilador, y se puede argumentar fácilmente en ambos sentidos.
Nota al margen: En estos casos, asuma siempre que el compilador hará lo contrario de lo que quería / necesitaba. Debe deshabilitar la optimización (al menos para este módulo) o intentar encontrar un comportamiento más definido para lo que desea. (Esta es también la razón por la que las pruebas unitarias son tan importantes) Si cree que es un defecto, debe comentarlo con los desarrolladores de C ++.
Todo esto sigue siendo muy difícil de leer, así que trato de incluir lo que creo que es relevante para que pueda leerlo usted mismo.
glvalue Una expresión glvalue es lvalue o xvalue.
Propiedades:
Un glvalue se puede convertir implícitamente en un prvalue con conversión implícita de lvalue a rvalue, matriz a puntero o función a puntero. Un glvalue puede ser polimórfico: el tipo dinámico del objeto que identifica no es necesariamente el tipo estático de la expresión. Un glvalue puede tener un tipo incompleto, donde lo permita la expresión.
xvalue Las siguientes expresiones son expresiones xvalue:
una llamada a función o una expresión de operador sobrecargada, cuyo tipo de retorno es rvalue referencia a objeto, como std :: move (x); a [n], la expresión de subíndice incorporada, donde un operando es una matriz rvalue; am, el miembro de la expresión de objeto, donde a es un rvalue ym es un miembro de datos no estáticos de tipo no de referencia; a. * mp, el puntero al miembro de la expresión del objeto, donde a es un rvalue y mp es un puntero al miembro de datos; un ? b: c, la expresión condicional ternaria para algunos b y c (ver definición para más detalles); una expresión de conversión a rvalue referencia al tipo de objeto, como static_cast (x); cualquier expresión que designe un objeto temporal, después de la materialización temporal. (desde C ++ 17) Propiedades:
Igual que rvalue (abajo). Igual que glvalue (abajo). En particular, como todos los rvalues, los xvalues se unen a las referencias de rvalue y, como todos los glvalues, los xvalues pueden ser polimórficos y los xvalues que no pertenecen a la clase pueden estar calificados como cv.
lvalue Las siguientes expresiones son expresiones lvalue:
el nombre de una variable, una función o un miembro de datos, independientemente del tipo, como std :: cin o std :: endl. Incluso si el tipo de la variable es una referencia rvalue, la expresión que consta de su nombre es una expresión lvalue; una llamada a función o una expresión de operador sobrecargada, cuyo tipo de retorno es referencia de valor l, como std :: getline (std :: cin, str), std :: cout << 1, str1 = str2 o ++ it; a = b, a + = b, a% = b, y todas las demás expresiones de asignación integradas y de asignación compuesta; ++ ay --a, las expresiones de preincremento y pre-decremento integradas; * p, la expresión de indirección incorporada; a [n] yp [n], las expresiones de subíndice integradas, excepto donde a es un rvalue de matriz (desde C ++ 11); am, el miembro de la expresión de objeto, excepto donde m es un enumerador de miembros o una función miembro no estática, o donde a es un rvalue ym es un miembro de datos no estático de tipo no de referencia; p-> m, el miembro incorporado de la expresión de puntero, excepto donde m es un enumerador de miembros o una función miembro no estática; a. * mp, el puntero al miembro de la expresión del objeto, donde a es un lvalue y mp es un puntero al miembro de datos; p -> * mp, el puntero incorporado al miembro de la expresión del puntero, donde mp es un puntero al miembro de datos; a, b, la expresión de coma incorporada, donde b es un valor l; un ? b: c, la expresión condicional ternaria para algunos byc (p. ej., cuando ambos son valores l del mismo tipo, pero consulte la definición para obtener más detalles); una cadena literal, como "¡Hola, mundo!"; una expresión de conversión al tipo de referencia lvalue, como static_cast (x); una llamada de función o una expresión de operador sobrecargada, cuyo tipo de retorno es rvalue referencia a la función; una expresión de conversión a rvalue referencia al tipo de función, como static_cast (x). (desde C ++ 11) Propiedades:
Igual que glvalue (abajo). Se puede tomar la dirección de un valor l: & ++ i 1
y & std :: endl son expresiones válidas. Se puede utilizar un lvalue modificable como operando de la izquierda de los operadores de asignación incorporados y de asignación compuesta. Se puede usar un lvalue para inicializar una referencia de lvalue; esto asocia un nuevo nombre con el objeto identificado por la expresión.
como si fuera una regla
El compilador de C ++ puede realizar cambios en el programa siempre que se cumpla lo siguiente:
1) En cada punto de secuencia, los valores de todos los objetos volátiles son estables (las evaluaciones anteriores están completas, las nuevas evaluaciones no se inician) (hasta C ++ 11) 1) Los accesos (lee y escribe) a los objetos volátiles ocurren estrictamente de acuerdo con la semántica de las expresiones en las que ocurren. En particular, no se reordenan con respecto a otros accesos volátiles en el mismo hilo. (desde C ++ 11) 2) Al finalizar el programa, los datos escritos en archivos son exactamente como si el programa se hubiera ejecutado como se escribió. 3) El texto de aviso que se envía a los dispositivos interactivos se mostrará antes de que el programa espere la entrada. 4) Si se admite ISO C pragma #pragma STDC FENV_ACCESS y se establece en ON,
Si desea leer las especificaciones, creo que estas son las que necesita leer
Referencias
Estándar C11 (ISO / IEC 9899: 2011): 6.7.3 Calificadores de tipo (p: 121-123)
Estándar C99 (ISO / IEC 9899: 1999): 6.7.3 Calificadores de tipo (p: 108-110)
Estándar C89 / C90 (ISO / IEC 9899: 1990): 3.5.3 Calificadores de tipo