C/C++ Changing the Value of a Const

C/C++ changing the value of a const

you need to cast away the constness:

linux ~ $ cat constTest.c
#include <stdio.h>

void modA( int *x )
{
*x = 7;
}

int main( void )
{

const int a = 3; // I promisse i won't change a
int *ptr;
ptr = (int*)( &a );

printf( "A=%d\n", a );
*ptr = 5; // I'm a liar, a is now 5
printf( "A=%d\n", a );

*((int*)(&a)) = 6;
printf( "A=%d\n", a );

modA( (int*)( &a ));
printf( "A=%d\n", a );

return 0;
}
linux ~ $ gcc constTest.c -o constTest
linux ~ $ ./constTest
A=3
A=5
A=6
A=7
linux ~ $ g++ constTest.c -o constTest
linux ~ $ ./constTest
A=3
A=3
A=3
A=3

also the common answer doesn't work in g++ 4.1.2

linux ~ $ cat constTest2.cpp
#include <iostream>
using namespace std;
int main( void )
{
const int a = 3; // I promisse i won't change a
int *ptr;
ptr = const_cast<int*>( &a );

cout << "A=" << a << endl;
*ptr = 5; // I'm a liar, a is now 5
cout << "A=" << a << endl;

return 0;
}
linux ~ $ g++ constTest2.cpp -o constTest2
linux ~ $ ./constTest2
A=3
A=3
linux ~ $

btw.. this is never recommended... I found that g++ doesn't allow this to happen.. so that may be the issue you are experiencing.

Updating const variable value in C++

Modifying a const value through any mechanism (including casting away const-ness) results in "undefined behavior" (UB) which means that you cannot reason about the behavior of the program. The compiler is allowed to assume that const values will never change. Based on that assumption, the compiler might:

  • Store const values in read-only memory pages; attempting assignment through the pointer would then cause an access violation.
  • Inline the const value wherever it is used. Because you take a pointer, the compiler will likely also emit some kind of storage for the value (on the stack most likely) so that it has a memory location that can be pointed to, but modifying this value will not cause the inlined values to change.
  • Something else, possibly including both of the above.

Which one it does can depend on the optimization level selected.

A program that assigns to a const value is effectively "nonsense" and has no meaningful interpretation.

Note that this is UB in both C and C++. You just need to twist the C++ compiler's arm a bit more to get the code to compile (int *ptr = const_cast<int *>(&var);). The C standard permits implicit discarding of a const qualifier in some contexts; the C++ standard is a lot more strict about this.

Most C compilers will emit a warning on int *ptr = &var; regarding discarding of the const qualifier. I would strongly recommend compiling with all warnings enabled and converted to errors (-Wall -Werror on gcc). That would cause the C compiler to also refuse to compile this code.


Note that casting const away from a pointer (or reference) and assigning to the target is not UB when the value was not declared const:

// non-const value
int x = 10;

// take a pointer to x and store it in a pointer-to-const
const int *y = &x;

// cast away the const-ness of the pointer target
int *z = const_cast<int *>(y);

// this is fine; z points at x which is not const
*z = 5;

However, if you find yourself needing const_cast then most likely you are either (1) doing some crazy template metaprogramming, or (2) approaching the problem wrong.

changing const value in C

It's allowed because you have overruled the constness of ptr1 by casting it to a non-const pointer. This is why casts can be very dangerous.

Note that some compilers, such as GCC, will not allow you to cast away const status like this.

Can we modify the value of a const variable?

The author's point is that declaring a variable with register storage class prevents you from taking its address, so it can not be passed to a function that might change its value by casting away const.

void bad_func(const int *p) {
int *q = (int *) p; // casting away const
*q = 42; // potential undefined behaviour
}

void my_func() {
int i = 4;
const int j = 5;
register const int k = 6;
bad_func(&i); // ugly but allowed
bad_func(&j); // oops - undefined behaviour invoked
bad_func(&k); // constraint violation; diagnostic required
}

By changing potential UB into a constraint violation, a diagnostic becomes required and the error is (required to be) diagnosed at compile time:

c11

5.1.1.3 Diagnostics


1 - A conforming implementation shall produce at least one diagnostic message [...] if a preprocessing translation unit or translation unit
contains a violation of any syntax rule or constraint, even if the behavior is also explicitly
specified as undefined or implementation-defined.

6.5.3.2 Address and indirection operators


Constraints

1 - The operand of the unary & operator shall be [...] an lvalue that designates an object that [...] is
not declared with the register storage-class specifier.

Note that array-to-pointer decay on a register array object is undefined behaviour that is not required to be diagnosed (6.3.2.1:3).

Note also that taking the address of a register lvalue is allowed in C++, where register is just an optimiser hint (and a deprecated one at that).

Is it possible to change value of a constant variable via reinterpret_cast?

The thing that is interesting for me, how the program output is 5, while debugger clearly indicates the value of a is 10?

This depends entirely on the compiler. It could output 10, 5, crash, ... because it is undefined behavior.

If you want to know why the output of the binary created by a particular compiler has a certain result for undefined behavior, you have to look at the generated output of the compiler. This can be done using e.g. godbolt.org

For your code the output gcc (11.2) generates is:

        push    rbp
mov rbp, rsp
sub rsp, 16
mov BYTE PTR [rbp-9], 5
lea rax, [rbp-9]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov BYTE PTR [rax], 10
mov esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
mov eax, 0
leave
ret

Here you can see that the compiler correctly assumes that the value of a will not change. And replaces std::cout << unsigned(a) << std::endl; with std::cout << unsigned(5) << std::endl;:

        mov     esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)

If you remove the const from a the output is:

        movzx   eax, BYTE PTR [rbp-9]
movzx eax, al
mov esi, eax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)


Related Topics



Leave a reply



Submit