Por que posso alterar uma variável const local por meio de lançamentos de ponteiro, mas não de um global em C?

Eu queria alterar o valor de uma constante usando pointers.

Considere o seguinte 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 esperado, o valor da constante é modificado.

mas quando eu tentei o mesmo código com uma constante global, estou recebendo o seguinte erro de tempo de execução. O repórter de travamento do Windows está abrindo. O executável é interrompido após a impressão da primeira instrução printf nesta declaração “* ptr_to_const = 20;”

Considere o seguinte 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 programa é compilado em ambiente mingw com codeblocks IDE.

Alguém pode explicar o que está acontecendo?

Está na memory só de leitura!

Basicamente, seu computador resolve endereços virtuais para físicos usando um sistema de tabela de página de dois níveis. Junto com essa grande estrutura de dados, vem um bit especial que representa se uma página é legível ou não. Isso é útil, porque os processos do usuário provavelmente não deveriam estar acima da criação de sua própria assembly (embora o código de auto-modificação seja legal). Claro, eles provavelmente também não deveriam estar escrevendo suas próprias variables ​​constantes.

Você não pode colocar uma variável de nível de function “const” em memory somente leitura, porque ela reside na pilha, onde DEVE estar em uma página de leitura / gravação. No entanto, o compilador / linker vê sua const e faz um favor colocando-a na memory somente leitura (é constante). Obviamente, sobrescrevendo isso causará todo o tipo de infelicidade para o kernel, que eliminará essa raiva no processo finalizando-o.

É uma constante e você está usando alguns truques para alterá-la de qualquer maneira, o que resulta em comportamentos indefinidos. A constante global é provavelmente em memory somente leitura e, portanto, não pode ser modificada. Quando você tenta fazer isso, você recebe um erro de execução.

A variável local constante é criada na pilha, que pode ser modificada. Então você se safa mudando a constante neste caso, mas ainda pode levar a coisas estranhas. Por exemplo, o compilador poderia ter usado o valor da constante em vários lugares em vez da constante em si, de modo que “alterar a constante” não mostre nenhum efeito nesses locais.

A expulsão da constância de ponteiro em C e C ++ é segura apenas se você tiver certeza de que a variável apontada era originalmente não-const (e, por acaso, você tem um ponteiro const para ela). Caso contrário, ele é indefinido e, dependendo do seu compilador, da fase da lua, etc, o primeiro exemplo pode muito bem falhar também.

Você não deve esperar que o valor seja modificado em primeiro lugar. De acordo com o padrão, é um comportamento indefinido. Está errado tanto com uma variável global quanto em primeiro lugar. Só não faça isso 🙂 Poderia ter batido de outra maneira, ou com local e global.

Existem dois erros aqui. O primeiro é:

 int *ptr_to_const = &const_val; 

que é uma violação de restrição de acordo com C11 6.5.4 / 3 (padrões anteriores tinham texto semelhante):

Restrições

Conversões que envolvam pointers, que não sejam permitidas pelas restrições do 6.5.16.1, devem ser especificadas por meio de uma

A conversão de const int * para int * não é permitida pelas restrições do 6.5.16.1 (que pode ser visto aqui ).

Confusamente, quando alguns compiladores encontram uma violação de restrição, eles escrevem “aviso” (ou até mesmo nada, dependendo dos switches) e fingem que você escreveu algo em seu código e continua. Isso geralmente leva a programas que não se comportam como o programador esperava ou, na verdade, não se comportam de maneira previsível. Por que os compiladores fazem isso? Bate-me, mas certamente contribui para um stream interminável de perguntas como esta.


gcc, parece proceder como se você tivesse escrito int *ptr_to_const = (int *)&const_val; .

Esse trecho de código não é uma violação de restrição porque é usada uma conversão explícita. No entanto, isso nos leva ao segundo problema. A linha *ptr_to_const = 20; Em seguida, tenta gravar em um object const . Isso causa um comportamento indefinido , o texto relevante do padrão está em 6.7.3 / 6:

Se for feita uma tentativa de modificar um object definido com um tipo com qualificação constante por meio do uso de um lvalue com um tipo não qualificado por const, o comportamento é indefinido.

Esta regra é um Semântico, não uma Restrição, o que significa que o Padrão não requer que o compilador emita qualquer tipo de aviso ou mensagem de erro. O programa é simplesmente errado e pode se comportar de maneira absurda, com qualquer tipo de sintomas estranhos, incluindo, mas não se limitando ao que você observou.

Como esse comportamento não está definido na especificação, ele é específico da implementação, portanto, não é portável, portanto, não é uma boa ideia.

Por que você quer mudar o valor de uma constante?

Nota: isto é pretendido como uma resposta para Podemos alterar o valor de um object definido com const através de pointers? que liga a esta questão como uma duplicata.

O Padrão não impõe requisitos sobre o que um compilador deve fazer com o código que constrói um ponteiro para um object const e tenta gravar nele. Algumas implementações – especialmente aquelas embutidas – podem possivelmente ter comportamentos úteis (por exemplo, uma implementação que usa RAM não volátil pode legitimamente colocar variables const em uma área de memory que é gravável, mas cujo conteúdo permanecerá mesmo se a unidade for desligada e back-up), e o fato de que o Padrão não impõe requisitos sobre como os compiladores lidam com códigos que criam pointers não- const para a memory const não afeta a legitimidade de tal código em implementações que expressamente permitem isso . Mesmo em tais implementações, no entanto, é provavelmente uma boa ideia replace 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 

com

 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(); 

Se um compilador estivesse propenso a aplicar “otimizações” indesejadas ao código que grava no armazenamento const , mover “protected_ram_store” para um módulo compilado separadamente poderia servir para evitar tais otimizações. Também poderia ser útil, por exemplo, o código precisa ser movido para o hardware que usa algum outro protocolo para gravar na memory. Alguns hardwares, por exemplo, podem usar protocolos de gravação mais complicados para minimizar a probabilidade de gravações erradas. Ter uma rotina cuja finalidade expressa é escrever para a memory “normalmente-const” tornará essas intenções claras.