简体   繁体   中英

Why can I change a local const variable through pointer casts but not a global one in C?

I wanted to change value of a constant by using pointers.

Consider the following code

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;
}

As expected the value of constant is modified.

but when I tried the same code with a global constant, I am getting following run time error. The Windows crash reporter is opening. The executable is halting after printing the first printf statement in this statement "*ptr_to_const = 20;"

Consider the following code

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;
}

This program is compiled in mingw environment with codeblocks IDE.

Can anyone explain what is going on?

It's a constant and you are using some tricks to change it anyway, so undefined behavior results. The global constant is probably in read-only memory and therefore cannot be modified. When you try to do that you get a runtime error.

The constant local variable is created on the stack, which can be modified. So you get away with changing the constant in this case, but it might still lead to strange things. For example the compiler could have used the value of the constant in various places instead of the constant itself, so that "changing the constant" doesn't show any effect in these places.

It's in read only memory!

Basically, your computer resolves virtual to physical addresses using a two level page table system. Along with that grand data structure comes a special bit representing whether or not a page is readable. This is helpful, because user processes probably shouldn't be over writing their own assembly (although self-modifying code is kind of cool). Of course, they probably also shouldn't be over writing their own constant variables.

You can't put a "const" function-level variable into read only memory, because it lives in the stack, where it MUST be on a read-write page. However, the compiler/linker sees your const, and does you a favor by putting it in read only memory (it's constant). Obviously, overwriting that will cause all kinds of unhappiness for the kernel who will take out that anger on the process by terminating it.

Casting away pointer const-ness in C and C++ is only safe if you are certain that the pointed-to variable was originally non-const (and you just happen to have a const pointer to it). Otherwise, it is undefined, and depending on your compiler, the phase of the moon, etc, the first example could very well fail as well.

There are two errors here. The first one is:

int *ptr_to_const = &const_val;

which is a constraint violation according to C11 6.5.4/3 (earlier standards had similar text):

Constraints

Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast

The conversion from const int * to int * is not permitted by the constraints of 6.5.16.1 (which can be viewed here ).

Confusingly, when some compilers encounter a constraint violation, they write "warning" (or even nothing at all, depending on switches) and pretend that you wrote something else in your code, and carry on. This often leads to programs that do not behave as the programmer expected, or in fact don't behave in any predictable way. Why do compilers do this? Beats me, but it certainly makes for an endless stream of questions like this.


gcc, appears to proceed as if you had written int *ptr_to_const = (int *)&const_val; .

This piece of code is not a constraint violation because an explicit cast is used. However this brings us to the second problem. The line *ptr_to_const = 20; then tries to write to a const object. This causes undefined behaviour , the relevant text from the Standard is in 6.7.3/6:

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

This rule is a Semantic, not a Constraint, which means that the Standard does not require the compiler to emit any sort of warning or error message. The program is just wrong and may behave in nonsensical ways, with any sort of strange symptoms, including but not limited to what you observed.

You should not even expect the value to be modified at the first place. According to the standard, it is undefined behavior. It is wrong both with a global variable and in the first place. Just don't do it :) It could have crashed the other way, or with both local and global.

Note: this is intended as an answer to Can we change the value of an object defined with const through pointers? which links to this question as a duplicate.

The Standard imposes no requirements on what a compiler must do with code that constructs a pointer to a const object and attempts to write to it. Some implementations--especially embedded ones--might possibly have useful behaviors (eg an implementation which uses non-volatile RAM could legitimately place const variables in an area of memory which is writable, but whose contents will remain even if the unit is powered down and back up), and the fact that the Standard imposes no requirements about how compilers handle code that creates non- const pointers to const memory does not affect the legitimacy of such code on implementations which expressly allow it . Even on such implementations, however, it's probably a good idea to replace something like:

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

with

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

If a compiler would be prone to apply unwanted "optimizations" to code which writes to const storage, moving "protected_ram_store" into a separately-compiled module could serve to prevent such optimizations. It could also be helpful eg the code needs to move to hardware which uses some other protocol to write to memory. Some hardware, for example, might use a more complicated write protocols to minimize the probability of erroneous writes. Having a routine whose express purpose is to write to "normally-const" memory will make such intentions clear.

Since this behavior is not defined in the specification, it is implementation-specific, so not portable, so not a good idea.

Why would you want to change the value of a constant?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM