I have read, it is undefined in some C standards (perhaps 99?) what happens when a const is modified. But a student presented me with some code, which I modified.
I cannot see anything special about the address of the constant variable a. I verified that &a and b are the same, so the compiler is not subtly pointing to some other location.
Yet when I assign *b, the const value does not change.
I am not running with optimization. When I compile with the -g flag to debug and step into the code, I get the results I expect (the memory location of the variable a changes). Yet the code presented below does not reflect the updated value of a.
Is this that temps are now being placed in registers even in debug mode, with no optimization?
#include <iostream>
using namespace std;
int main(){
const int a = 15;
cout << a << '\n';
int * b= (int*)&a;
cout << &a << "\n";
cout << b << "\n";
*b = 20;
cout << *b << '\n';
cout << a << '\n';
int x = a;
cout << x << '\n';
x = *b;
cout << x << '\n';
return 1;
}
This is also undefined behavior in C++, we can see this by going to the draft C++ standard section 7.1.6.1 The cv-qualifiers paragraph 4 which says:
[...]any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.
Undefined behavior means the results are unpredictable which effectively means any result is possible even ones that at first glance defy intuition.
A quick experiment with godbolt using -O0 so there is no optimization going on shows the compiler is just using the literal value 15 for a instead of retrieving it from memory and printing that out:
movl $15, %esi #,
So the compiler is performing constant folding since it assumes that since a is constant it can just use the value 15 anywhere it sees a. Which is completely reasonable since you told it a was constant.
Related
Code
#include <iostream>
using namespace std;
int main() {
const int N = 22;
int * pN = const_cast<int*>(&N);
*pN = 33;
cout << N << '\t' << &N << endl;
cout << *pN << '\t' << pN << endl;
}
Output
22 0x22ff74
33 0x22ff74
Why are there two different values at the same address?
Why are there two different datas at the same address?
There aren't. The compiler is allowed to optimize any mention of a const to be as though you had written its compile-time value in there.
Note that the compiler is also allowed to generate code that erases your hard disk when you run it if you do nasty tricks like writing to memory reserved for consts.
You get undefined behavior on the line *pN = 33;, because you're modifying a const value. Anything can happen. Don't do it.
Likely, though, your compiler simply optimized. In the line:
cout << N << '\t' << &N << endl;
It knows N is a constant expression with the value 22, so just changes the line to:
cout << 22 << '\t' << &N << endl;
And on your next line, you fetch the value at the address of N, which you "set" to 33. (But really, all you did was remove any guarantees about the state of your program.)
By stating that N is const, you have promised that you won't modify it. And then you go and modify it. This breaks one of the assumptions the compiler is making, and as a result, the program behaves incorrectly.
This is referred to as "undefined behavior" - after violating an assumption in the language, the behavior of the program is completely undefined. It need not have produced that output - it could've produced 33 for both, or 42, or crashed, or erased your hard drive, or summoned demons through your nasal passages. So, don't modify const values :)
int * pN = const_cast<int*>(&N);
*pN = 33;
Your code invokes Undefined Behavior1 because you are modifying the content of a const qualified variable/object.
1)
Undefined Behavior: Behavior, such as might arise upon use of an
erroneous program construct or of erroneous data, for which the
Standard imposes no requirements.[Note: permissible undefined behavior
ranges from ignoring the situation completely with unpredictable
results, to behaving during translation or program execution in a
documented manner characteristic of the environment (with or without
the issuance of a diagnostic message), to terminating a translation
or execution (with the issuance of a diagnostic message).
You can declare N as volatile, to force the compiler to fetch the current value from the variable's memory location.
volatile const int N = 22;
const_cast in your code, just hands over a pointer 'Pn' to 'N' through which 'N' can be modified. The address of 'N' remains the same as the handed over pointer 'Pn'
I had the same question (Why am I not able to modify the contents of const int even with const_cast<int*>?) . I think answered here wonderfully by everyone. Just adding the assembly output from compiler
This is my original code
const int y = 7;
int* a = new int;
a = const_cast<int*>(&y);
*a = 8;
std::cout << (int)*(&y) << std::endl;
This is the assembly output
std::cout << (int)*(&y) << std::endl;
00381CB6 push offset std::endl<char,std::char_traits<char> > (03813C5h)
**00381CBB push 7**
00381CBD mov ecx,dword ptr [_imp_?cout#std##3V?$basic_ostream#DU?$char_traits#D#std###1#A (03900ACh)]
00381CC3 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03900B8h)]
00381CC9 mov ecx,eax
00381CCB call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03900BCh)]
So the compiler will just replace the const variables with its actual value during compile time.
Code
#include <iostream>
using namespace std;
int main() {
const int N = 22;
int * pN = const_cast<int*>(&N);
*pN = 33;
cout << N << '\t' << &N << endl;
cout << *pN << '\t' << pN << endl;
}
Output
22 0x22ff74
33 0x22ff74
Why are there two different values at the same address?
Why are there two different datas at the same address?
There aren't. The compiler is allowed to optimize any mention of a const to be as though you had written its compile-time value in there.
Note that the compiler is also allowed to generate code that erases your hard disk when you run it if you do nasty tricks like writing to memory reserved for consts.
You get undefined behavior on the line *pN = 33;, because you're modifying a const value. Anything can happen. Don't do it.
Likely, though, your compiler simply optimized. In the line:
cout << N << '\t' << &N << endl;
It knows N is a constant expression with the value 22, so just changes the line to:
cout << 22 << '\t' << &N << endl;
And on your next line, you fetch the value at the address of N, which you "set" to 33. (But really, all you did was remove any guarantees about the state of your program.)
By stating that N is const, you have promised that you won't modify it. And then you go and modify it. This breaks one of the assumptions the compiler is making, and as a result, the program behaves incorrectly.
This is referred to as "undefined behavior" - after violating an assumption in the language, the behavior of the program is completely undefined. It need not have produced that output - it could've produced 33 for both, or 42, or crashed, or erased your hard drive, or summoned demons through your nasal passages. So, don't modify const values :)
int * pN = const_cast<int*>(&N);
*pN = 33;
Your code invokes Undefined Behavior1 because you are modifying the content of a const qualified variable/object.
1)
Undefined Behavior: Behavior, such as might arise upon use of an
erroneous program construct or of erroneous data, for which the
Standard imposes no requirements.[Note: permissible undefined behavior
ranges from ignoring the situation completely with unpredictable
results, to behaving during translation or program execution in a
documented manner characteristic of the environment (with or without
the issuance of a diagnostic message), to terminating a translation
or execution (with the issuance of a diagnostic message).
You can declare N as volatile, to force the compiler to fetch the current value from the variable's memory location.
volatile const int N = 22;
const_cast in your code, just hands over a pointer 'Pn' to 'N' through which 'N' can be modified. The address of 'N' remains the same as the handed over pointer 'Pn'
I had the same question (Why am I not able to modify the contents of const int even with const_cast<int*>?) . I think answered here wonderfully by everyone. Just adding the assembly output from compiler
This is my original code
const int y = 7;
int* a = new int;
a = const_cast<int*>(&y);
*a = 8;
std::cout << (int)*(&y) << std::endl;
This is the assembly output
std::cout << (int)*(&y) << std::endl;
00381CB6 push offset std::endl<char,std::char_traits<char> > (03813C5h)
**00381CBB push 7**
00381CBD mov ecx,dword ptr [_imp_?cout#std##3V?$basic_ostream#DU?$char_traits#D#std###1#A (03900ACh)]
00381CC3 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03900B8h)]
00381CC9 mov ecx,eax
00381CCB call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03900BCh)]
So the compiler will just replace the const variables with its actual value during compile time.
After compilation, what does the reference become, an address, or a constant pointer?
I know the difference between pointers and references, but I want to know the difference between the underlying implementations.
int main()
{
int a = 1;
int &b = a;
int *ptr = &a;
cout << b << " " << *ptr << endl; // 1 1
cout << "&b: " << &b << endl; // 0x61fe0c
cout << "ptr: " << ptr << endl; // 0x61fe0c
return 0;
}
The pedantic answer is: Whatever the compiler feels like, all that matters is that it works as specified by the language's semantics.
To get the actual answer, you have to look at resulting assembly, or make heavy usage of Undefined Behavior. At that point, it becomes a compiler-specific question, not a "C++ in general" question
In practice, references that need to be stored essentially become pointers, while local references tend to get compiled out of existence. The later is generally the case because the guarantee that references never get reassigned means that if you can see it getting assigned, then you know full well what it refers to. However, you should not be relying on this for correctness purposes.
For the sake of completeness
It is possible to get some insight into what the compiler is doing from within valid code by memcpying the contents of a struct containing a reference into a char buffer:
#include <iostream>
#include <array>
#include <cstring>
struct X {
int& ref;
};
int main() {
constexpr std::size_t x_size = sizeof(X);
int val = 12;
X val_ref = {val};
std::array<unsigned char, x_size> raw ;
std::memcpy(&raw, &val_ref, x_size);
std::cout << &val << std::endl;
std::cout << "0x";
for(const unsigned char c : raw) {
std::cout << std::hex << (int)c;
}
std::cout << std::endl ;
}
When I ran this on my compiler, I got the (endian flipped) address of val stored within the struct.
it heavily depend on compiler maybe compiler decide to optimize the code therefore it will make it value or ..., but as far i know references will compiler like pointer i mean if you see their result assembly they are compiled like pointer.
I was messing around in C++ and wrote this code
#include <iostream>
int main() {
const int x = 10;
const int* ptr = &x;
*((int*)ptr) = 11;
std::cout << "ptr -> " << ptr << " -> " << *((int*)ptr) << std::endl;
std::cout << "x -> " << &x << " -> " << x << std::endl;
}
Interestingly, the output in the console is:
ptr -> 00CFF77C -> 11
x -> 00CFF77C -> 10
I checked the value at the memory address of ptr and the value is 11, so does this imply that const variables store the value in them, and not in memory? And if this were the case, why have a memory address storing the value?
Your code has Undefined Behavior.
x is a compile time constant, so the compiler expects it to not change value at runtime. As such, the compiler is allowed to optimize code by substituting uses of x's value with the literal value 10 at compile time.
So, at the point where << x is used, your compiler is emitting code for << 10 instead.
But, since your code is also using &x, the compiler is required to make sure that x has a memory address accessible at runtime. That means allocating a physical int and initializing its value to 10. Which you are then fiddling with in an unsafe manner, despite the fact that most compilers will likely place that int in read-only memory.
That is why you are seeing the output you see. But you are lucky your code is not crashing outright, trying to assign a new value to a constant at runtime.
The behavior of a program that writes to a const variable is undefined. It could print "10", it could print "11", it could print "3822", it could crash, or it could make demons fly out of your nose.
That said, in this case the behavior makes perfect sense. The compiler knows x is never allowed to change, so it transforms your std::cout << x to basically std::cout << 10.
Why does my code for overwriting const int variable work? Is it safe?
#include <iostream>
#include <cstring>
using namespace std;
int z = 5;
const int x = *(&z);
int main()
{
cout << "A:" << x << ", " << &x << endl;
int y = 7;
cout << "B:" << y << ", " << &y << endl;
memcpy((int*)&x, &y, sizeof(int));
cout << "C:" << x << ", " << &x << endl;
}
Output would be:
A:5, 0x600f94
B:7, 0x7a7efb68019c
C:7, 0x600f94
I am not sure if this has been asked before since I don't know what to search for in this situation.
Answering your questions:
It's not safe; should never be done, it's example of how not to use C.
It's based on undefined behaviour; that means specification doesn't give exact instruction how such attempt should be treated in the code.
Final answer to question why it works? The answer here is true for the GCC only as other compilers may optimise/treat const different way. You need to understand that technically const int x is a declaration of a variable with qualifier. That means (until it's optimised) it has it's place in the memory (in some circumstances in read-only section). When you removed the qualifier and give the address of the variable to the memcpy() (which is dummy library call unaware of memory protection) it makes attempt to write the new data to that address. If compiler puts that variable into read-only section (I faced that epic failure in the past) the execution on any Unix would end with segmentation fault caused by writing instruction violation memory protection of the read-only memory segment used by your program to hold constant data.
In C++ real constants are qualified by constexpr, however there are other implications.