簡體   English   中英

在C ++ 2017中將const轉換為非const指針並對其進行修改時,編譯器將兩個值都存儲在哪里?

[英]When casting a const to a non-const pointer in C++ 2017 and modifying it, where does the compiler store both values?

在Visual C ++ 2017中,當嘗試破壞規則時會發生什么時,我發現如果將const int強制轉換為int *,然后將值重新分配給int *,調試器將更改const的值,但是運行時執行不會。

無論我是在Debug模式下還是作為已發布的可執行文件運行,都會發生這種情況。 我知道它是未定義的,但是正在尋找關於這些值的存放位置的信息,因為它們似乎是相同的位置。

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

這兩個值存儲在哪里? 這就像有一個桶同時充滿兩種不同的液體一樣。

我知道這是未定義的行為,但是我想知道是否有人可以內部了解正在發生的事情。

前提是有缺陷的。 調試器按照相同的C ++ 17規則工作,因此它也可以假定不存在未定義行為。 這意味着它可以檢查源代碼並知道 j==100 沒有理由必須檢查運行時值。

如果一個對象位於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個字節,而不會影響程序的語義。 但是,該標准沒有提供任何方法,使程序員無法表明代碼要么不比較那些地址,要么在碰巧相等的情況下不會受到不利影響,因此,編譯器只能在可以替換的情況下進行此類替換。告訴被替換對象的地址不會暴露於可能執行此類比較的代碼中。

好吧,只看生成的程序集...

    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+114h 這是相對於全局初始化程序( __xt_z可能是調試器找到的最接近的符號)末尾的偏移量,最有可能到只讀數據段( .rdata )中。

那是Debug版本放100 (畢竟這是一個常數)。

然后,MSVC Debug版本總是在堆棧上分配局部變量和常量,因此您獲得了一個單獨的j變量,您甚至可以對其進行修改(請注意,編譯器在讀取j時不必從中讀取變量,因為它知道j是包含100的常量。

如果在發布模式下嘗試相同的操作,我們將看到編譯器進行了值傳播並優化了兩個變量,只需將值內聯到代碼中即可:

    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變量保持不變。

有關系嗎? 不,您不應依賴此行為,也不應修改常量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM