variable significado reservadas programacion palabras lenguaje ejemplos dev claves c++ language-lawyer c++17 volatile

significado - programacion en dev c++ ejemplos



¿Se permite que un compilador optimice una variable volátil local? (6)

Creo que nunca he visto una variable local usando volatile que no fuera un indicador de volatilidad. Como en:

int fn() { volatile int *x = (volatile int *)0xDEADBEEF; *x = 23; // request data, 23 = temperature return *x; // return temperature }

Los únicos otros casos de volatilidad que conozco utilizan un global que está escrito en un controlador de señales. No hay punteros involucrados allí. O acceda a los símbolos definidos en una secuencia de comandos de vinculador para estar en direcciones específicas relevantes para el hardware.

Es mucho más fácil razonar allí por qué la optimización alteraría los efectos observables. Pero la misma regla se aplica para su variable volátil local. El compilador debe comportarse como si el acceso a x fuera observable y no pudiera optimizarlo.

¿Se permite al compilador optimizar esto (de acuerdo con el estándar C ++ 17):

int fn() { volatile int x = 0; return x; }

¿a esto?

int fn() { return 0; }

¿Si es así por qué? ¿Si no, porque no?

Aquí hay algunas reflexiones sobre este tema: los compiladores actuales compilan fn() como una variable local colocada en la pila y luego la devuelven. Por ejemplo, en x86-64, gcc crea esto:

mov DWORD PTR [rsp-0x4],0x0 // this is x mov eax,DWORD PTR [rsp-0x4] // eax is the return register ret

Ahora, por lo que sé, el estándar no dice que una variable local volátil debe colocarse en la pila. Por lo tanto, esta versión sería igualmente buena:

mov edx,0x0 // this is x mov eax,edx // eax is the return ret

Aquí, edx almacena x . Pero ahora, ¿por qué parar aquí? Como edx y eax son cero, podríamos decir:

xor eax,eax // eax is the return, and x as well ret

Y transformamos fn() a la versión optimizada. ¿Es esta transformación válida? Si no, ¿qué paso no es válido?


Este bucle se puede optimizar mediante la regla as-if porque no tiene un comportamiento observable:

for (unsigned i = 0; i < n; ++i) { bool looped = true; }

Este no puede:

for (unsigned i = 0; i < n; ++i) { volatile bool looped = true; }

El segundo bucle hace algo en cada iteración, lo que significa que el bucle lleva tiempo O (n). No tengo idea de lo que es la constante, pero puedo medirla y luego tengo una forma de hacer un bucle ocupado durante un tiempo (más o menos) conocido.

Puedo hacerlo porque la norma dice que el acceso a los volátiles debe ocurrir, en orden. Si un compilador decidiera que en este caso el estándar no se aplicaba, creo que tendría derecho a presentar un informe de error.

Si el compilador elige colocarlo en un registro, supongo que no tengo un buen argumento en contra de eso. Pero todavía debe establecer el valor de ese registro en 1 para cada iteración de bucle.


No. El acceso a objetos volatile se considera un comportamiento observable, exactamente como E / S, sin distinción particular entre locales y globales.

Los requisitos mínimos para una implementación conforme son:

  • El acceso a objetos volatile se evalúa estrictamente de acuerdo con las reglas de la máquina abstracta.

[...]

Estos colectivamente se conocen como el comportamiento observable del programa.

N3690, [ejecución de intro], ¶8

La forma exacta en que esto se puede observar está fuera del alcance de la norma, y ​​cae directamente en el territorio específico de la implementación, exactamente como E / S y acceso a objetos volatile globales. volatile significa "crees que sabes todo lo que sucede aquí, pero no es así; confía en mí y haz esto sin ser demasiado inteligente, porque estoy en tu programa haciendo mis cosas secretas con tus bytes". Esto se explica en realidad en [dcl.type.cv] ¶7:

[Nota: volatile es una sugerencia a la implementación para evitar una optimización agresiva que involucre al objeto porque el valor del objeto podría cambiarse de manera indetectable por una implementación. Además, para algunas implementaciones, la volatilidad puede indicar que se requieren instrucciones especiales de hardware para acceder al objeto. Ver 1.9 para semántica detallada. En general, se pretende que la semántica de volatile sea la misma en C ++ que en C. - nota final]


Pido disentir con la opinión de la mayoría, a pesar de la plena comprensión de que volatile significa E / S observable.

Si tienes este código:

{ volatile int x; x = 0; }

Creo que el compilador puede optimizarlo bajo la regla as-if , asumiendo que:

  1. De lo contrario, la variable volatile no se hace visible externamente a través de, por ejemplo, los punteros (lo que obviamente no es un problema aquí ya que no existe tal cosa en el ámbito dado)

  2. El compilador no le proporciona un mecanismo para acceder externamente a ese volatile

La razón es simplemente que no se pudo observar la diferencia de todos modos, debido al criterio # 2.

Sin embargo, en su compilador, el criterio # 2 puede no estar satisfecho ! El compilador puede intentar proporcionarle garantías adicionales sobre la observación de variables volatile desde el "exterior", como al analizar la pila. En tales situaciones, el comportamiento es realmente observable, por lo que no se puede optimizar.

Ahora la pregunta es, ¿es el siguiente código diferente al anterior?

{ volatile int x = 0; }

Creo que he observado diferentes comportamientos para esto en Visual C ++ con respecto a la optimización, pero no estoy completamente seguro por qué motivo. Puede ser que la inicialización no cuente como "acceso"? No estoy seguro. Puede que valga la pena hacer una pregunta por separado si está interesado, pero de lo contrario, creo que la respuesta es como expliqué anteriormente.


Solo voy a agregar una referencia detallada para la regla de simulación y la palabra clave volatile . (En la parte inferior de estas páginas, siga las instrucciones "ver también" y "Referencias" para rastrear las especificaciones originales, pero me parece que cppreference.com es mucho más fácil de leer / entender).

Particularmente, 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 de miembro, etc.) que se realiza a través de una expresión de tipo glvalue de tipo calificado volátil se trata como un efecto secundario visible para los fines de optimización (es decir, dentro de un solo hilo de ejecución, los accesos no pueden optimizarse ni reordenarse con otro efecto lateral visible secuenciado 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, vea std :: memory_order ). Cualquier intento de referirse 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) resulta en un comportamiento indefinido.

Por lo tanto, la palabra clave volátil específicamente trata de deshabilitar la optimización del compilador en glvalues . Lo único que puede afectar la palabra clave volátil es, posiblemente, return x , el compilador puede hacer lo que quiera con el resto de la función.

En qué medida el compilador puede optimizar 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. , pero es leer y escribir en la pila, que debería poder racionalizarse. Por lo tanto, a medida que lo leo, esta es un área gris en la que el compilador puede optimizar, y se puede argumentar fácilmente en ambos sentidos.

Nota al margen: En estos casos, siempre asuma que el compilador hará lo contrario de lo que quería / necesitaba. Debe desactivar 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 la prueba de unidad es tan importante) Si crees que es un defecto, deberías mencionarlo con los desarrolladores de C ++.

Todo esto todavía es muy difícil de leer, así que tratar de incluir lo que creo que es relevante para que pueda leerlo usted mismo.

glvalue Una expresión de glvalue es lvalue o xvalue.

Propiedades:

Un valor glico se puede convertir implícitamente a un valor predeterminado con una conversión implícita de valor-valor-valor, 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 de 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 un valor de matriz; am, el miembro de expresión de objeto, donde a es un rvalue y m 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 valor y mp es un puntero al miembro de datos; una ? b: c, la expresión condicional ternaria para algunos byc (ver definición para detalles); una expresión de conversión para valorar la 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 valores de r, los valores de x se unen a las referencias de valor de r, y como todos los valores de gl, los valores de x pueden ser polimórficos, y los valores de no clase pueden ser calificados por CV.

lvalue Las siguientes expresiones son expresiones de 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 referencia rvalue, la expresión que consiste en su nombre es una expresión lvalue; una llamada de función o una expresión de operador sobrecargada, cuyo tipo de retorno es lvalue reference, 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 integrada y de asignación; ++ a y --a, las expresiones integradas de preincremento y decremento; * p, la expresión indirecta incorporada; a [n] yp [n], las expresiones de subíndices incorporadas, excepto cuando a es un valor de matriz (desde C ++ 11); am, el miembro de la expresión de objeto, excepto cuando m es un enumerador miembro o una función de miembro no estático, o donde a es un valor ym es un miembro de datos no estáticos de tipo no de referencia; p-> m, el miembro incorporado de la expresión de puntero, excepto cuando m es un enumerador miembro 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 lvalue; una ? b: c, la expresión condicional ternaria para algunos b y c (por ejemplo, cuando ambos son valores l del mismo tipo, pero vea la definición para 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 reference to function; una expresión de conversión a valor de referencia al tipo de función, como static_cast (x). (desde C ++ 11) Propiedades:

Igual que glvalue (abajo). La dirección de un lvalue puede tomarse: & ++ i as-if y & std :: endl son expresiones válidas. Se puede usar un lvalor modificable como el operando de la izquierda de los operadores de asignación integrada y de asignación integrada. Se puede usar un valor de l para inicializar una referencia de valor de l; esto asocia un nuevo nombre con el objeto identificado por la expresión.

como si la regla

Al compilador de C ++ se le permite realizar cualquier cambio en el programa siempre que se cumpla lo siguiente:

1) En cada punto de la secuencia, los valores de todos los objetos volátiles son estables (las evaluaciones anteriores están completas, las nuevas evaluaciones no se iniciaron) (hasta C ++ 11) 1) Los accesos (lecturas y escrituras) a los objetos volátiles ocurren estrictamente de acuerdo con la semántica de las expresiones en las que se producen. 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 estaba escrito. 3) El texto de solicitud que se envía a los dispositivos interactivos se mostrará antes de que el programa espere la entrada. 4) Si el pragma # pragma STDC FENV_ACCESS de ISO es compatible y está activado, los operadores aritméticos de punto flotante garantizan que los cambios en el entorno de punto flotante (modos de redondeo y excepciones de punto flotante) las llamadas se ejecutan como si estuvieran escritas, excepto que el resultado de cualquier expresión de punto flotante distinta de la conversión y la asignación puede tener un rango y una precisión de un tipo de punto flotante diferente del tipo de la expresión (ver FLT_EVAL_METHOD) a pesar de los resultados intermedios anteriores. de cualquier expresión de punto flotante se puede calcular como para un rango y precisión infinitos (a menos que #pragma STDC FP_CONTRACT esté APAGADO)

Si quieres leer las especificaciones, creo que estas son las que necesitas leer

Referencias

Estándar C11 (ISO / IEC 9899: 2011): 6.7.3 Calificadores de tipo (p: 121-123)

Norma 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


Teóricamente, un controlador de interrupciones podría

  • compruebe si la dirección de retorno se encuentra dentro de la función fn() . Puede acceder a la tabla de símbolos o a los números de línea de origen mediante la instrumentación o la información de depuración adjunta.
  • luego cambie el valor de x , que se almacenaría en un desplazamiento predecible desde el puntero de pila.

... haciendo que fn() devuelva un valor distinto de cero.