Search code examples
c++constantsconst-cast

How const prevents writing to specific memory [Clang, Mac OS]


I'm messing around with C++, reading some books about good habits in this language. I had read about const_cast, and wrote simple program which answer question : can I strip const prefix and write value to this address ?

My code :

#include <iostream>
#include <iomanip>

int main ()
{
    const int a = 5;
    std::cout << std::hex << "&a = " << std::showbase << std::setfill('0') << &a << "\n";
    int * b = const_cast<int *> (&a);
    std::cout << std::hex << std::setfill('0') << "b = " << b << "\n";
    std::cout << "Writing to address of a ..." << "\n";
    *b = 10;
    std::cout << "&a == b ? : " << ((&a == b) ? "True" : "False") << std::endl;
    std::cout << a << std::endl;
    std::cout << *b << std::endl;
    return 0;
}

Output :

00:35|domin568[35] ~/Desktop/experiments/newcpp $ ./const
&a = 0x7ffee465c528
b = 0x7ffee465c528
Writing to address of a ...
&a == b ? : True
0x5
0xa

This situation made me curious, they operate on the same address but they have different values ? Let's debug it with lldb !

00:37|domin568[39] ~/Desktop/experiments/newcpp $ lldb const
(lldb) b const.cpp:11
...
(lldb) r
Process 32578 launched: '/Users/domin568/Desktop/experiments/newcpp/const' (x86_64)
&a = 0x7ffeefbff478
b = 0x7ffeefbff478
Writing to address of a ...
Process 32578 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000dcb const`main at const.cpp:11
   8        int * b = const_cast<int *> (&a);
   9        std::cout << std::hex << std::setfill('0') << "b = " << b << "\n";
   10       std::cout << "Writing to address of a ..." << "\n";
-> 11       *b = 10;
   12       std::cout << "&a == b ? : " << ((&a == b) ? "True" : "False") << std::endl;
   13       std::cout << a << std::endl;
   14       std::cout << *b << std::endl;
Target 0: (const) stopped.
(lldb) p *b
(int) $0 = 5
(lldb) p b
(int *) $1 = 0x00007ffeefbff478
(lldb) p a
(const int) $2 = 5
(lldb) p &a
(const int *) $3 = 0x00007ffeefbff478
(lldb) s
Process 32578 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x0000000100000dd8 const`main at const.cpp:12
   9        std::cout << std::hex << std::setfill('0') << "b = " << b << "\n";
   10       std::cout << "Writing to address of a ..." << "\n";
   11       *b = 10;
-> 12       std::cout << "&a == b ? : " << ((&a == b) ? "True" : "False") << std::endl;
   13       std::cout << a << std::endl;
   14       std::cout << *b << std::endl;
   15       return 0;
Target 0: (const) stopped.
(lldb) p *b
(int) $4 = 10
(lldb) p *a
error: indirection requires pointer operand ('int' invalid)
(lldb) p a
(const int) $5 = 10
(lldb) s
&a == b ? : True
Process 32578 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x0000000100000e34 const`main at const.cpp:13
   10       std::cout << "Writing to address of a ..." << "\n";
   11       *b = 10;
   12       std::cout << "&a == b ? : " << ((&a == b) ? "True" : "False") << std::endl;
-> 13       std::cout << a << std::endl;
   14       std::cout << *b << std::endl;
   15       return 0;
   16   }
Target 0: (const) stopped.
(lldb) s
0x5      <---- wtf ? changed runtime ?

Could someone explain me what's behind the scene ? I know that's probably undefined behavior but why it behaves like that ?

Thanks in advance !


Solution

  • The const keyword in C++ is how you tell the compiler that you won't be modifying the value of the variable. The value could still change, but the code operating on the const variable won't be doing the changing.

    By declaring a variable to be be const does two things. First it causes the compiler to double check that you aren't changing the const variable. If you try and modify the variable it'll cause a compilation error. The second thing it does is allows the compiler to make some assumptions when it optimizes the code.

    This second bit is probably why the two values reported in your program differ. The compiler sees that a is const, so it replaces every use of it with the value 5. To know for sure what exactly the compiler is outputting you'll need to inspect the assembly.

    As you discovered, the language does provide ways for you to remove the const-ness, but it's almost never the right thing to do. As you have seen, using const_cast can have unexpected results.