Как const предотвращает запись в определенную память [Clang, Mac OS]

Я балуюсь с C++, читаю книги о хороших привычках на этом языке. Я читал про const_cast и написал простую программу, которая отвечает на вопрос: можно ли убрать префикс const и записать значение по этому адресу?

Мой код:

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

Выход :

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

Эта ситуация меня заинтересовала, они работают по одному и тому же адресу, но имеют разные значения? Давайте отладим его с помощью 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 ?

Может ли кто-нибудь объяснить мне, что стоит за сценой? Я знаю, что это, вероятно, неопределенное поведение, но почему оно так себя ведет?

Заранее спасибо !


person Domin568    schedule 16.09.2018    source источник


Ответы (1)


Ключевое слово const в C++ — это то, как вы сообщаете компилятору, что не будете изменять значение переменной. Значение все еще может измениться, но код, работающий с константной переменной, этого не сделает.

Объявление переменной константой делает две вещи. Во-первых, компилятор дважды проверяет, не изменяете ли вы константную переменную. Если вы попытаетесь изменить переменную, это вызовет ошибку компиляции. Во-вторых, он позволяет компилятору делать некоторые предположения при оптимизации кода.

Этот второй бит, вероятно, является причиной того, что два значения, сообщаемые в вашей программе, различаются. Компилятор видит, что a является константой, поэтому он заменяет каждое его использование значением 5. Чтобы точно знать, что именно выводит компилятор, вам нужно проверить сборку.

Как вы обнаружили, язык предоставляет способы избавиться от константности, но это почти никогда не бывает правильным. Как вы видели, использование const_cast может привести к неожиданным результатам.

person bobshowrocks    schedule 16.09.2018
comment
Это еще не все. Если объект неконстантный и передается как константный параметр, то вы можете const_cast отказаться от константности и записать ее в функцию. Если объект const, то вы не можете const_cast избавиться от константности. Последнее является неопределенным поведением. int OP - это const int a = 5;, поэтому const не может быть const_cast далеко. Здесь где-то есть хороший Q&A, но я не могу его найти. - person jww; 17.09.2018
comment
Верно. Я бы сказал, что только потому, что вы можете отказаться от const на законных основаниях в некоторых обстоятельствах, не означает, что вы должны это делать. Я ожидаю, что в подавляющем большинстве случаев рефакторинг кода или реализация второй функции, которая не принимает const, будет предпочтительным путем вперед. - person bobshowrocks; 17.09.2018