[英]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.