Can you explain this behaviour to me, pls? Here the code:
int* b = new int;
const int MAX_AGE = 90;
b = (int*)&MAX_AGE;
std::cout << b << std::endl;
std::cout << &MAX_AGE << std::endl;
std::cout << *b << std::endl;
std::cout << MAX_AGE << std::endl;
std::cout << "........." << std::endl;
*b = 2;
std::cout << *b << std::endl; // HERE I get 2, that's ok
std::cout << MAX_AGE << std::endl; // HERE I still get 90, why?
std::cout << b << std::endl;
std::cout << &MAX_AGE << std::endl;
The problem is that you lied to your compiler, and compilers are pretty good at exacting revenge on people who lie to them.
Specifically, on this line you told the compiler that MAX_AGE is changeable:
b = (int*)&MAX_AGE;
This is a lie, because you declared MAX_AGE to be a const. What happens next is called undefined behavior (UB): the compiler is free to produce any results, including complete nonsense, after your code triggers UB.
In your case, however, there is a pretty good explanation of what gets printed: knowing that MAX_AGE is another name for 90, the compiler has optimized std::cout << MAX_AGE << std::endl; to print 90, without looking up its value from memory.
MAX_AGE is declared as const int. With your c-style cast you remove constness and then proceed to modify a const value. This is UB.
This is a prime example for why this is UB: Due to the constness of MAX_AGE the compiler knows that it won't change and can thus replace all occurences of it by the literal 90.
const tells the compiler that the variable MAX_AGE should be stored in the write protected region of the respective segment in memory.
Armed with this knowledge the compiler can obviate the need to repeatedly read the same memory location. In other words the compiler might cache the constant value. That is why you see MAX_AGE showing up with the original value.
Anyway as has been already mentioned you shouldn't be confusing the compiler with your actual intentions. If you intend to store a variable in a write protected region then you shouldn't be modifying it.
Related
I'm just decided to test malloc and new. Here is a code:
#include <iostream>
#include <string>
struct C
{
int a = 7;
std::string str = "super str";
};
int main()
{
C* c = (C*)malloc(sizeof(C));
std::cout << c->a << "\n";
std::cout << c->str << "\n";
free(c);
std::cout << "\nNew:\n\n";
c = new C();
std::cout << c->a << "\n";
std::cout << c->str << "\n";
}
Why an output of this program stops at std::cout << c->a << "\n";:
-842150451
C:\Code\Temp\ConsoleApplication12\x64\Debug\ConsoleApplication12.exe (process 22636) exited with code 0.
Why does compiler show no errors - I thought, std::string isn't initialized properly in case of malloc, so it should break something.
If I comment out printing of the string, I'm getting a full output:
-842150451
New:
7
super str
C:\Code\Temp\ConsoleApplication12\x64\Debug\ConsoleApplication12.exe (process 21652) exited with code 0.
I use MSVS2022.
You've used malloc. One of the reasons to not do this is that it hasn't actually initialized your object. It's just allocated memory for it. As a result, when accessing member fields, you get undefined behavior.
You have also forgotten to delete the C object you created with new. But you may wish to use a std::unique_ptr in this scenario, to avoid having to explicitly delete the object at all. The smart pointer will automatically free the memory when it goes out of scope at the end of main.
auto c = std::make_unique<C>();
std::cout << c->a << std::endl;
std::cout << c->str << std::endl;
(C*) is an Explicit Conversion. It tells the compiler to shut up and do exactly what it was told to do, no matter how stupid that may be. You don't get an error message because the code explicitly told the compiler not to give you one.
To allow the programmer and compiler the maximum amount of leeway, C++ will not prevent you from doing some pretty insane things. It assumes you know what you are doing and are not trying to shoot yourself.
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.
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.