¿Por qué puedo cambiar una variable const local a través de modelos de puntero pero no uno global en C?

Quería cambiar el valor de una constante mediante el uso de punteros.

Considera el siguiente código

int main() { const int const_val = 10; int *ptr_to_const = &const_val; printf("Value of constant is %d",const_val); *ptr_to_const = 20; printf("Value of constant is %d",const_val); return 0; } 

Como se esperaba, el valor de la constante se modifica.

pero cuando probé el mismo código con una constante global, recibo el siguiente error de tiempo de ejecución. El reportero de locking de Windows se está abriendo. El ejecutable se detiene después de imprimir la primera instrucción printf en esta statement “* ptr_to_const = 20;”

Considera el siguiente código

 const int const_val = 10; int main() { int *ptr_to_const = &const_val; printf("Value of constant is %d",const_val); *ptr_to_const = 20; printf("Value of constant is %d",const_val); return 0; } 

Este progtwig está comstackdo en un entorno mingw con bloques de código IDE.

¿Puede alguien explicar qué está pasando?

¡Está en la memoria de solo lectura!

Básicamente, su computadora resuelve direcciones virtuales a físicas utilizando un sistema de tabla de páginas de dos niveles. Junto con esa gran estructura de datos viene un bit especial que representa si una página es legible o no. Esto es útil, porque los procesos del usuario probablemente no deberían ser más de escribir su propio ensamblaje (aunque el código de auto modificación es genial). Por supuesto, probablemente tampoco deberían estar escribiendo sus propias variables constantes.

No puede poner una variable de nivel de función “const” en memoria de solo lectura, porque vive en la stack, donde DEBE estar en una página de lectura y escritura. Sin embargo, el comstackdor / enlazador ve tu const, y te hace un favor al ponerlo en la memoria de solo lectura (es constante). Obviamente, sobrescribir eso causará todo tipo de infelicidad para el núcleo que eliminará ese enojo en el proceso al terminarlo.

Es una constante y está utilizando algunos trucos para cambiarla de todos modos, por lo que resulta un comportamiento indefinido. La constante global probablemente esté en la memoria de solo lectura y, por lo tanto, no se puede modificar. Cuando intenta hacer eso, obtiene un error de tiempo de ejecución.

La variable local constante se crea en la stack, que se puede modificar. Así que te saldrás con la modificación de la constante en este caso, pero aún podría llevar a cosas extrañas. Por ejemplo, el comstackdor podría haber usado el valor de la constante en varios lugares en lugar de la constante misma, de modo que “cambiar la constante” no muestra ningún efecto en estos lugares.

Eliminar la identidad del puntero en C y C ++ solo es seguro si estás seguro de que la variable apuntada fue originalmente no const (y por casualidad tienes un puntero const). De lo contrario, no está definido, y dependiendo de tu comstackdor, la fase de la luna, etc., el primer ejemplo también podría fallar.

Ni siquiera debería esperar que el valor se modifique en primer lugar. De acuerdo con el estándar, es un comportamiento indefinido. Es incorrecto tanto con una variable global como en primer lugar. Simplemente no lo hagas 🙂 Pudo haberse estrellado en el otro sentido, o con ambos locales y globales.

Hay dos errores aquí. El primero es:

 int *ptr_to_const = &const_val; 

que es una violación de restricción de acuerdo con C11 6.5.4 / 3 (las normas anteriores tenían un texto similar):

Restricciones

Las conversiones que involucren punteros, distintas de las permitidas por las restricciones de 6.5.16.1, deberán especificarse mediante un molde explícito

La conversión de const int * a int * no está permitida por las restricciones de 6.5.16.1 (que se pueden ver aquí ).

Confusamente, cuando algunos comstackdores encuentran una violación de restricción, escriben “advertencia” (o incluso nada en absoluto, dependiendo de los interruptores) y fingen que usted escribió algo más en su código, y continúan. Esto a menudo conduce a progtwigs que no se comportan como esperaba el progtwigdor, o de hecho no se comportan de manera predecible. ¿Por qué los comstackdores hacen esto? Me gana, pero sin duda es una fuente interminable de preguntas como esta.


gcc, parece proceder como si hubiera escrito int *ptr_to_const = (int *)&const_val; .

Este fragmento de código no es una violación de restricción porque se usa un lanzamiento explícito. Sin embargo, esto nos lleva al segundo problema. La línea *ptr_to_const = 20; luego intenta escribir en un objeto const . Esto causa un comportamiento indefinido , el texto relevante del Estándar está en 6.7.3 / 6:

Si se intenta modificar un objeto definido con un tipo const-qualified mediante el uso de un lvalue con un tipo no const calificado, el comportamiento no está definido.

Esta regla es una semántica, no una restricción, lo que significa que el estándar no requiere que el comstackdor emita ningún tipo de advertencia o mensaje de error. El progtwig es simplemente incorrecto y puede comportarse de manera absurda, con cualquier tipo de síntomas extraños, que incluyen pero no se limitan a lo que usted observó.

Como este comportamiento no está definido en la especificación, es específico de la implementación, por lo que no es portátil, por lo que no es una buena idea.

¿Por qué querrías cambiar el valor de una constante?

Nota: esto es una respuesta a ¿Podemos cambiar el valor de un objeto definido con const a través de punteros? que enlaza a esta pregunta como un duplicado.

El Estándar no impone requisitos sobre qué debe hacer un comstackdor con el código que construye un puntero a un objeto const e intenta escribir en él. Algunas implementaciones, especialmente las integradas, pueden tener comportamientos útiles (por ejemplo, una implementación que utiliza RAM no volátil podría ubicar legítimamente variables const en un área de memoria que puede escribirse, pero cuyo contenido permanecerá incluso si la unidad está apagada y copia de seguridad), y el hecho de que el Estándar no impone requisitos sobre cómo los comstackdores manejan código que crea punteros no const para const memory no afecta la legitimidad de dicho código en implementaciones que lo permiten expresamente . Incluso en tales implementaciones, sin embargo, probablemente sea una buena idea reemplazar algo como:

 volatile const uint32_t action_count; BYPASS_WRITE_PROTECT = 0x55; // Hardware latch which enables writing to BYPASS_WRITE_PROTECT = 0xAA; // const memory if written with 0x55/0xAA BYPASS_WRITE_PROTECT = 0x04; // consecutively followed by the bank number *((uint32_t*)&action_count)++; BYPASS_WRITE_PROTECT = 0x00; // Re-enable write-protection of const storage 

con

 void protected_ram_store_u32(uint32_t volatile const *dest, uint32_t dat) { BYPASS_WRITE_PROTECT = 0x55; // Hardware latch which enables writing to BYPASS_WRITE_PROTECT = 0xAA; // const memory if written with 0x55/0xAA BYPASS_WRITE_PROTECT = 0x04; // consecutively followed by the bank number *((volatile uint32_t*)dest)=dat; BYPASS_WRITE_PROTECT = 0x00; // Re-enable write-protection of const storage } void protected_ram_finish(void) {} ... protected_ram_store(&action_count, action_count+1); protected_ram_finish(); 

Si un comstackdor sería propenso a aplicar “optimizaciones” no deseadas al código que escribe en el almacenamiento const , mover “protected_ram_store” a un módulo comstackdo por separado podría evitar tales optimizaciones. También podría ser útil, por ejemplo, el código necesita moverse a hardware que usa algún otro protocolo para escribir en la memoria. Algunos hardware, por ejemplo, pueden usar protocolos de escritura más complicados para minimizar la probabilidad de escrituras erróneas. Tener una rutina cuyo propósito expreso es escribir en la memoria “normalmente const” hará que esas intenciones sean claras.