При приведении const к неконстантному указателю в C ++ 2017 и его изменении, где компилятор хранит оба значения? - PullRequest
0 голосов
/ 20 января 2019

В Visual C ++ 2017, экспериментируя с тем, что происходит, когда вы нарушаете правила, я обнаружил, что если я приведу const int к int *, а затем переназначу значение в int *, отладчик изменит значениеconst, но выполнение во время выполнения не будет.

Это происходит независимо от того, запускаю ли я его в режиме отладки или как выпущенный исполняемый файл.Мне известно, что оно не определено, но я ищу информацию о том, где хранятся эти значения, поскольку они кажутся идентичными.

const int j = 100;
//int *q = &j; //Compiler disallows
int *q = (int*)&j; //By some magic, now allowed
*q = 300; //After this line, j = 300 in debugger
cout << "j = " << j << endl; //300 in debugger, 100 in console
//^ What is happening here? Where are the two values stored?
cout << "*q = " << *q << endl; //300 in both

//Output:
//  j = 100
//  *q = 300

Где хранятся два значения?Это все равно, что иметь одно ведро, одновременно заполненное двумя разными жидкостями.

Я знаю, что это неопределенное поведение, но мне было интересно, сможет ли кто-нибудь пролить свет на происходящее внутри.

Ответы [ 3 ]

0 голосов
/ 21 января 2019

Если объект находится в const хранилище, компилятор может на досуге заменить его двумя или более объектами с одинаковым содержимым, если он может сказать, что адреса никогда не сравниваются. Компилятор, как правило, не сможет сделать это, если адреса обоих объектов будут открыты для внешнего мира, но может сделать это в тех случаях, когда один объект открыт, а другой (-ы) нет.

Рассмотрим, например:

const char Hey[4] = "Hey";

void test(int index)
{
  char const *HeyPtr = Hey;
  putchar(HeyPtr[index]);
}

Компилятор, обрабатывающий test, сможет увидеть, что значение HeyPtr никогда не подвергается внешнему коду каким-либо образом, и на некоторых платформах может быть полезно использование функции test, использующей свою собственную копию строка. На платформе, где адреса 64-битные, если test не включает свою собственную копию строки, тогда восемь байтов должны были бы содержать адрес Hey. Четыре байта, необходимые для хранения дополнительной копии строки, будут стоить меньше, чем восемь байтов, необходимых для хранения адреса.

В некоторых ситуациях Стандарт предлагает гарантии, более надежные, чем обычно требуется программистам. Например, учитывая:

const int foo[] = {1,2,3,4};
const int bar[] = {1,2,3,4};

Если программа не сравнивает foo (или адрес, полученный из нее) с bar (аналогично), использование одного и того же хранилища для обоих объектов позволит сэкономить 16 байт без влияния на семантику программы. Стандарт, однако, не предоставляет средств, с помощью которых программист мог бы указать, что код либо не будет сравнивать эти адреса, либо не будет подвергаться неблагоприятному воздействию, если они окажутся равными, поэтому компилятор может делать такие замены только в тех случаях, когда он может это сделать. сказать, что адрес замещенного объекта не будет открыт для кода, который может выполнять такие сравнения.

0 голосов
/ 21 января 2019

Ну просто посмотрите на сгенерированную сборку ...

    const int j = 100;
00052F50  mov         dword ptr [j],64h  
    //int *q = &j; //Compiler disallows
    int *q = (int*)&j; //By some magic, now allowed
00052F58  lea         rax,[j]  
00052F5D  mov         qword ptr [q],rax  
    *q = 300; //After this line, j = 300 in debugger
00052F62  mov         rax,qword ptr [q]  
00052F67  mov         dword ptr [rax],12Ch  
    cout << "j = " << j << endl; //300 in debugger, 100 in console
00052F6D  lea         rdx,[__xt_z+114h (07FF679CC6544h)]  
00052F74  lea         rcx,[std::cout (07FF679D31B80h)]  
00052F7B  call        std::operator<<<std::char_traits<char> > (07FF679B43044h)  
00052F80  mov         edx,64h  
00052F85  mov         rcx,rax  
00052F88  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF679B417E9h)  
00052F8D  lea         rdx,[std::endl<char,std::char_traits<char> > (07FF679B42C25h)]  
00052F94  mov         rcx,rax  
00052F97  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF679B445F7h)  
    //^ What is happening here? Where are the two values stored?
    cout << "*q = " << *q << endl; //300 in both
00052F9C  lea         rdx,[__xt_z+11Ch (07FF679CC654Ch)]  
00052FA3  lea         rcx,[std::cout (07FF679D31B80h)]  
00052FAA  call        std::operator<<<std::char_traits<char> > (07FF679B43044h)  
00052FAF  mov         rcx,qword ptr [q]  
00052FB4  mov         edx,dword ptr [rcx]  
00052FB6  mov         rcx,rax  
00052FB9  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF679B417E9h)  
00052FBE  lea         rdx,[std::endl<char,std::char_traits<char> > (07FF679B42C25h)]  
00052FC5  mov         rcx,rax  
00052FC8  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF679B445F7h)  

Обратите внимание на "странное" прочтение из __xt_z+114h. Это смещение от конца глобальных инициализаторов (__xt_z, вероятно, является ближайшим символом, найденным отладчиком), скорее всего, в разделе данных только для чтения (.rdata).

Вот где версия Debug ставит 100 (в конце концов, это константа).

Затем версия отладки MSVC всегда выделяет локальные переменные и константы в стеке, следовательно, вы получаете отдельную переменную j, которую вы можете даже изменить (обратите внимание, что компилятору не нужно читать из нее, когда вы читаете j, поскольку он знает, что j является константой, содержащей 100).

Если мы попробуем то же самое в режиме Release, мы увидим, что компилятор сделал распространение значений и оптимизировал обе переменные, просто вставив значения в код:

    const int j = 100;
    //int *q = &j; //Compiler disallows
    int *q = (int*)&j; //By some magic, now allowed
    *q = 300; //After this line, j = 300 in debugger
    cout << "j = " << j << endl; //300 in debugger, 100 in console
000C101D  lea         rdx,[string "j = " (07FF72FAC3298h)]  
000C1024  mov         rcx,qword ptr [__imp_std::cout (07FF72FAC30A8h)]  
000C102B  call        std::operator<<<std::char_traits<char> > (07FF72FAC1110h)  
000C1030  mov         edx,64h  
000C1035  mov         rcx,rax  
000C1038  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF72FAC30A0h)]  
000C103E  lea         rdx,[std::endl<char,std::char_traits<char> > (07FF72FAC12E0h)]  
000C1045  mov         rcx,rax  
000C1048  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF72FAC3098h)]  
    //^ What is happening here? Where are the two values stored?
    cout << "*q = " << *q << endl; //300 in both
000C104E  lea         rdx,[string "*q = " (07FF72FAC32A0h)]  
000C1055  mov         rcx,qword ptr [__imp_std::cout (07FF72FAC30A8h)]  
000C105C  call        std::operator<<<std::char_traits<char> > (07FF72FAC1110h)  
000C1061  mov         edx,12Ch  
000C1066  mov         rcx,rax  
000C1069  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF72FAC30A0h)]  
000C106F  lea         rdx,[std::endl<char,std::char_traits<char> > (07FF72FAC12E0h)]  
000C1076  mov         rcx,rax  
000C1079  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF72FAC3098h)]  

В обоих случаях вывод одинаковый. Переменная const остается неизменной.

Имеет ли это значение? Нет, вы не должны полагаться на это поведение и не должны изменять константы.

0 голосов
/ 20 января 2019

Предпосылка ошибочна.Отладчик работает по тем же правилам C ++ 17, поэтому он также может предположить, что не существует неопределенного поведения.Это означает, что он может проверить исходный код и знать j==100.Нет причин для проверки значения времени выполнения.

...