referencias referencia punteros por paso parametros funciones como c++ c pointers language-lawyer volatile

c++ - punteros - ¿El acceso a un objeto no volátil declarado a través de una referencia/puntero volátil confiere reglas volátiles sobre dichos accesos?



punteros en c (4)

¿El acceso a un objeto no volátil declarado a través de una referencia / puntero volátil confiere reglas volátiles sobre dichos accesos?

volatile no significa lo mismo en C & C ++. El estándar C ++ hace que los accesos a través de valores volátiles sean observables. [1] Dice que pretende que esto sea lo mismo que el comportamiento de C. Y ese es el comportamiento descrito en la C racionalidad. Sin embargo, el Estándar C dice que los accesos a objetos declarados volátiles son observables. (Tenga en cuenta que el acceso a un objeto declarado volátil a través de un valor no volátil no está definido).

Sin embargo. Existe un informe de defectos que esencialmente tiene un acuerdo del comité (aunque aún está abierto) que el Estándar debería decir, y que la intención siempre ha sido, y que las implementaciones siempre han reflejado, que no es la volatilidad de un objeto lo que importa (según el Estándar) pero de la volatilidad de (el lvalor de) un acceso (según la justificación).

Resumen de informe de defectos para C11 Versión 1.10 Fecha: abril de 2016 DR 476 semántica volátil para valores de valores de 04/2016 Abierto

Por supuesto, lo que se hace sobre el comportamiento observable depende de la implementación.

Realmente no hay ninguna ambigüedad. Es solo que la gente no puede creer que el comportamiento del Estándar C sea lo que es, porque ese no es el uso histórico pre- volatile (cuando los valores de los literales de la dirección se consideraron objetos volátiles), como se pretende en la Razón, según lo implementado por los compiladores antes y después, según lo interpretado y descrito por el Estándar C ++, tal como se corrigió en el DR De manera similar, el estándar es claro en que no dice que los accesos no volátiles son observables, por lo que no lo son. (Y "efecto secundario" es un término utilizado para definir el orden parcial de evaluación).

[1] O al menos eso espero ahora. De un comentario de :

Para C ++, vea también P0612R0: NB comentario CH 2: volatile , que se adoptó este mes para limpiar algunos comentarios sobrantes sobre "objetos volátiles" en el estándar de C ++, cuando realmente se accede a través de glvalues ​​volátiles es lo que significó (como, presumiblemente / con suerte, lo que C significaba).

Este será largo, ya que para contextualizarlo y proporcionar la mayor cantidad de información posible, debo deambular a través de varios enlaces y citas, ya que a menudo es la única forma de ingresar al Agujero de Conejo Estándar de C / C ++. Si tiene mejores citas o alguna otra mejora en esta publicación, hágamelo saber. Pero para resumir por adelantado, puedes culpar a @zwol por que publique esto ;-) y el objetivo es encontrar la verdad entre dos proposiciones:

  • ¿Los estándares C y (por importación; ver comentarios) requieren que los accesos a través de volatile * o volatile & refieran a un objeto originalmente declarado volatile para tener una semántica volatile ?
  • ¿O es el acceso a un objeto calificado no volatile través de un puntero / referencia volatile suficiente / se supone que hace que dichos accesos se comporten como si el objeto fuera declarado volatile ?

Y de cualquier manera, si (como parece) la redacción es algo ambigua en comparación con la intención, ¿podemos aclarar esto en los Estándares?

La primera de estas interpretaciones mutuamente excluyentes se realiza más comúnmente, y eso no es completamente sin base. Sin embargo, espero mostrar que hay una cantidad significativa de "dudas razonables" a favor de la segunda, especialmente cuando volvemos a algunos pasajes anteriores en los Documentos de Razonamiento y WG.


Sabiduría aceptada: el objeto referido en sí debe haber sido declarado volatile

Pregunta popular de ayer ¿Es la definición de "volátil" tan volátil, o GCC está teniendo algunos problemas de cumplimiento estándar? surgió al suponer que una referencia volatile conferiría un comportamiento volatile a un referente no volatile , pero al encontrar que no lo hizo, o lo hizo en diversos grados y de manera impredecible.

La respuesta aceptada inicialmente llegó a la conclusión de que solo importaba el tipo declarado del referente. Este y la mayoría de los comentarios parecían estar de acuerdo en que los principios equivalentes están en juego como sabemos bien para const : el comportamiento solo sería volatile (o se definirá en absoluto) si la referencia tiene la misma calificación cv que el objeto referido:

La palabra clave en ese pasaje es objeto. volatile sig_atomic_t flag; Es un objeto volátil. *(volatile char *)foo es simplemente un acceso a través de un valor calificado de volatile y el estándar no requiere que tenga ningún efecto especial . - zwol

Esta interpretación parece ser ampliamente aceptada, como se ve en las respuestas a esta pregunta similar pero con suerte no duplicada: Requisitos para el comportamiento de puntero a volátil que apunta a un objeto no volátil Pero hay incertidumbre incluso allí: justo después la respuesta dice "no", luego dice "tal vez"! De todos modos ... revisemos el Estándar para ver en qué se basan los no.


Lo que dice la norma ... o no

C11, N1548, §6.7.3 : Si bien está claro que es UB acceder a un objeto definido con tipo volatile o const través de un puntero que no comparte dicho calificador ...

6 Si se intenta modificar un objeto definido con un tipo cualificado por const mediante el uso de un lvalue con tipo no calificado por const , el comportamiento es indefinido. Si se intenta referirse a un objeto definido con un tipo calificado como volatile mediante el uso de un lvalue con un tipo calificado no volatile- , el comportamiento no está definido. (133)

... el Estándar no parece mencionar explícitamente el escenario opuesto, es decir, para volatile . Además, al resumir la volatile y las operaciones al respecto, ahora se refiere a un objeto que tiene un tipo cualificado por volatile :

7 Un objeto que tiene un tipo calificado volatile puede modificarse de formas desconocidas para la implementación o tener otros efectos secundarios desconocidos. Por lo tanto, cualquier expresión que se refiera a dicho objeto se evaluará estrictamente de acuerdo con las reglas de la máquina abstracta, como se describe en 5.1.2.3. Además, en cada punto de la secuencia, el último valor almacenado en el objeto coincidirá con el prescrito por la máquina abstracta, excepto según lo modificado por los factores desconocidos mencionados anteriormente (134). Implementación definida.

¿Debemos asumir que "tiene" es equivalente a "se definió con"? o puede "tiene" referirse a una combinación de calificadores de objeto y referencia?

Un comentarista resumió el problema con este tipo de redacción:

De n1548 §6.7.3 ¶6, el estándar usa la frase "objeto definido con un tipo calificado volátil" para distinguirlo de "lvalue con tipo calificado volátil". Es desafortunado que este "objeto definido con la distinción" versus "lvalue" no se transfiera, y el estándar luego usa "objeto que tiene un tipo calificado volátil", y dice que "lo que constituye el acceso a un objeto que tiene un tipo calificado volátil está definido por la implementación "(que podría haber dicho" lvalue "o" objeto definido con "para mayor claridad). Oh bien. - Dietrich Epp

El párrafo 4 de la misma sección parece ser menos frecuente, pero podría ser relevante, como veremos en la siguiente sección.


Duda razonable: ¿Está / estuvo un puntero / referencia volatile destinado a conferir semántica volatile en su desreferencia?

La respuesta antes mencionada tiene un comentario en el que el autor cita una declaración anterior del Comité que pone en duda la idea de "la referencia debe coincidir con el referente":

Curiosamente, hay una oración allí [ R99 Fundamentos de la volatile ] que implica que el comité quiso que la *(volatile T*)x obligue a que un acceso a x sea ​​tratado como volátil; pero la redacción real de la norma no logra esto. - zwol

Podemos encontrar un poco más de información en este bit de la Razón fundamental, desde el segundo subproceso mencionado anteriormente: Requisitos para el comportamiento de puntero a volátil apuntando a objeto no volátil

Por otro lado, esta publicación cita el 6.7.3 de la Justificación de la Norma Internacional - Lenguajes de programación - C:

Una conversión de un valor a un tipo calificado no tiene efecto; La calificación (volátil, digamos) no puede tener ningún efecto en el acceso, ya que ha ocurrido antes del caso. Si es necesario acceder a un objeto no volátil utilizando semántica volátil, la técnica consiste en convertir la dirección del objeto al tipo de puntero a calificado apropiado, y luego eliminar esa referencia.

- philipxy

Y de ese hilo de bytes , nos referimos a C99 s6.7.3 p3, también conocido como p11 de C11, y este análisis:

El párrafo en cuestión está justo antes de la sección 6.7.3.1 en el documento racional. Si también necesita citar el documento estándar, cite 6.7.3 p3:

Las propiedades asociadas con tipos calificados son significativas solo para expresiones que son valores l.

La expresión (volatile WHATEVER) non_volatile_object_identifier no es un lvalor, por lo tanto, el calificador "volátil" no tiene sentido.

A la inversa, la expresión * (volatile WHATEVER *) & non_volatile_object_identifier es un lvalue (se puede colocar en el lado izquierdo de una declaración de asignación), por lo que la propiedad del calificador "volátil" tiene su significado en este caso.

- Tim Rentsch

Hay una demostración muy específica que apoya esta idea, con respecto específico a la primera pregunta vinculada, en el documento WG N1381 . Esto introdujo el memset_s() anexado para hacer lo que el OP quería: garantizar el llenado sin memoria de la memoria. Al analizar las posibles implementaciones, parece apoyar la idea, al omitir indicar cualquier requisito, que el uso de un puntero volatile para alterar un objeto no volatile debe generar un código basado en el calificador del puntero , independientemente del objeto referido. ..

  1. Solución de ''memset seguro'' independiente de la plataforma:

void *secure_memset(void *v, int c , size_t n) { volatile unsigned char *p = v; while (n--) *p++ = c; return v; }

Este enfoque evitará que la eliminación de memoria se optimice, y debería funcionar en cualquier plataforma compatible con el estándar.

... y que los compiladores que no hacen esto están en aviso ...

Recientemente se ha informado que algunos compiladores violan la norma al no respetar siempre el calificador volatile .


Quien tiene razon

Eso fue agotador. Ciertamente hay mucho espacio para la interpretación aquí, dependiendo de qué documentos haya leído y cuáles no, y cómo elija interpretar muchas palabras que no son lo suficientemente específicas. Parece claro que algo anda mal: o bien:

  • la Razón fundamental y el N1381 están redactados de manera incorrecta o al azar, o
  • fueron invalidados específicamente de forma retroactiva ... o
  • La Norma está redactada errónea o al azar.

Espero que podamos hacerlo mejor que toda la ambigüedad y la especulación que parece haber rodeado esto en el pasado, y que una declaración más concluyente quede registrada. Para ese fin, cualquier otra fuente e ideas de los expertos serían bienvenidas.


El Estándar no intenta definir todos los comportamientos que serían necesarios en una implementación útil. El razonamiento reconoce explícitamente la posibilidad de que una implementación pueda ser simultánea y casi totalmente inútil.

El Estándar clasifica la semántica de volatile accesos volatile como definidos por la implementación, y de ninguna manera, la forma o la forma requieren que una implementación deba definirlos de manera útil. Por lo tanto, no sería irrazonable decir que siempre que los comportamientos documentados y reales concuerden, la implementación de semánticas volatile como las de gcc haría que una implementación no fuera conforme, sino que simplemente la haría inútil para los fines para los cuales podría haber sido adecuada.

Tenga en cuenta que gcc se usa a menudo en plataformas en las que es posible configurar una región arbitraria de espacio de direcciones que se comporte como un dispositivo de E / S y luego se comporte como RAM ordinaria. Por lo tanto, puede ser necesario asegurar que ciertas operaciones se secuencian de manera muy precisa, aunque la secuenciación de la mayoría de las demás operaciones no importaría; exigir que todas las operaciones en algo se traten como volatile para que cualquier operación así tratada no parece una buena receta para la optimización.

Lo que me parece extraño es que la gente se haya interesado tanto en los últimos años en si el Estándar permite que los compiladores implementen semánticas inútiles para algunas construcciones, con el propósito de mejorar el rendimiento del código que no requiere que las construcciones sean útiles, cuando tales un enfoque parecería ser inferior en casi todos los aspectos a la implementación de semánticas útiles de manera predeterminada, pero proporcionar a los programadores que no necesitan dicha semántica para eliminarlos mediante el uso de interruptores de línea de comandos o directivas #pragma. Si un programa incluye una directiva [hipotética] #pragma gcc_loose_volatile , gcc puede hacer lo que quiera sin importar cómo interpretaría los requisitos de la Norma en materia de volatile , y si no incluye dicha directiva, la semántica inútil sería inútil sin importar Si la Norma los prohibió o no.


Un objeto es volátil o no. Hacer referencia a un objeto utilizando una referencia volátil producirá un código que es correcto ya sea que el objeto sea volátil o no.


convertido para responder porque creo que una no-respuesta reflexiva puede ayudar a descubrir la verdad aquí.

Supongo que la pregunta subyacente es "¿qué tan abstracto esperamos que sea el modelo de memoria?". Al calificar un puntero que no es vol. Como volátil, parece que le estamos pidiendo al compilador que "escriba directamente en la E / S o en la memoria". Eso está bien, pero si el compilador ha deducido previamente que la "memoria" no necesita existir, ¿qué debería hacer? Retroceder y crear la memoria, o ignorarte?

Me parece que estos dos casos siguientes tienen una intención muy diferente:

E / S mapeada en memoria

volatile unsigned char * const uart_base = (volatile unsigned char *)0x10000;

Esto claramente pretende informar al compilador que hay un mapa de memoria de uart en la dirección 0x10000.

Borrado de hashes de contraseña

void *secure_memset(void *v, int c , size_t n) { volatile unsigned char *p = v; while (n--) *p++ = c; return v; }

Esto está claramente destinado a garantizar que la memoria en v to (int *) v + n se modifique realmente antes de que la función regrese.

Sin embargo, no está claro si una llamada a esta función podría ser eliminada si se dedujera que la memoria en v nunca fue necesaria.

Yo diría que si previamente en el programa, se dedujo que la memoria no necesita existir en absoluto, entonces no me sorprendería si la llamada fuera eluida, sin importar la conversión a volátil.

Gracias. Debido a que se toma la dirección, ¿no se requiere que el objeto ocupe la memoria?

gcc parece estar de acuerdo contigo

#include <cstdint> #include <cstring> void * clearmem(void* p, std::size_t len) { auto vp = reinterpret_cast<volatile char*>(p); while (len--) { *vp++ = 0; } return p; } struct A { char sensitive[100]; A(const char* p) { std::strcpy(sensitive, p); } ~A() { clearmem(&sensitive[0], 100); } }; void use_privacy(A a) { auto b = a; } int main() { A a("very private"); use_privacy(a); }

rendimientos

clearmem(void*, unsigned long): leaq (%rdi,%rsi), %rax testq %rsi, %rsi je .L4 .L5: movb $0, (%rdi) addq $1, %rdi cmpq %rax, %rdi jne .L5 .L4: xorl %eax, %eax ret use_privacy(A): leaq -120(%rsp), %rax leaq 100(%rax), %rdx .L10: movb $0, (%rax) addq $1, %rax cmpq %rdx, %rax jne .L10 ret main: leaq -120(%rsp), %rax leaq 100(%rax), %rdx .L13: movb $0, (%rax) addq $1, %rax cmpq %rdx, %rax jne .L13 leaq -120(%rsp), %rax leaq 100(%rax), %rdx .L14: movb $0, (%rax) addq $1, %rax cmpq %rdx, %rax jne .L14 leaq -120(%rsp), %rax leaq 100(%rax), %rdx .L15: movb $0, (%rax) addq $1, %rax cmpq %rdx, %rax jne .L15 xorl %eax, %eax ret

clang no impide la construcción de arreglos privados, por lo que no puedo sacar conclusiones allí.