[英]Why is it allowed to cast a pointer to a reference?
最初是這個問題的主題,它出現了OP只是忽略了dereference。 同時, 這個答案讓我和其他一些人思考 - 為什么允許使用C風格的演員或reinterpret_cast
投射指向引用的指針?
int main() {
char c = 'A';
char* pc = &c;
char& c1 = (char&)pc;
char& c2 = reinterpret_cast<char&>(pc);
}
上面的代碼在編譯時沒有在Visual Studio中的任何警告或錯誤(關於投),而GCC只會給你一個警告,如圖所示這里 。
我的第一個想法是指針以某種方式自動被取消引用(我正常使用MSVC,所以我沒有得到GCC顯示的警告),並嘗試了以下內容:
#include <iostream>
int main() {
char c = 'A';
char* pc = &c;
char& c1 = (char&)pc;
std::cout << *pc << "\n";
c1 = 'B';
std::cout << *pc << "\n";
}
這里顯示了非常有趣的輸出。 因此,您似乎正在訪問指向變量,但與此同時,您不是。
想法? 解釋嗎? 標准報價?
嗯,這就是reinterpret_cast
的目的! 顧名思義,該轉換的目的是將內存區域重新解釋為另一種類型的值。 因此,使用reinterpret_cast
您始終可以將一種類型的左值轉換為另一種類型的引用。
這在語言規范的5.2.10 / 10中描述。 它還說reinterpret_cast<T&>(x)
與*reinterpret_cast<T*>(&x)
。
在這種情況下,您正在投射指針這一事實完全並不完全無關緊要。 不,指針不會自動解除引用(考慮到*reinterpret_cast<T*>(&x)
解釋,甚至可能會說相反的情況:該指針的地址是自動獲取的)。 在這種情況下,指針只是“占據內存中某些區域的某個變量”。 該變量的類型無論如何都沒有區別。 它可以是double
,指針, int
或任何其他左值。 該變量僅被視為您重新解釋為另一種類型的內存區域。
至於C風格的演員表 - 在這種情況下它只被解釋為reinterpret_cast
,所以上面的內容立即適用於它。
在第二個示例中,您將引用c
附加到指針變量pc
占用的內存中。 當你執行c = 'B'
,你強行將值'B'
寫入該內存,從而完全破壞原始指針值(通過覆蓋該值的一個字節)。 現在,被破壞的指針指向一些不可預測的位置。 后來你試圖取消引用被破壞的指針。 在這種情況下發生的事情是純粹的運氣。 程序可能會崩潰,因為指針通常是不可分的。 或者你可能會很幸運,並使你的指針指向一些不可預測但有效的位置。 在這種情況下,程序將輸出一些東西。 沒有人知道它會輸出什么,並且它沒有任何意義。
可以將第二個程序重寫為沒有引用的等效程序
int main(){
char* pc = new char('A');
char* c = (char *) &pc;
std::cout << *pc << "\n";
*c = 'B';
std::cout << *pc << "\n";
}
從實際角度來看,在little-endian平台上,您的代碼將覆蓋指針的最低有效字節。 這樣的修改不會使指針指向太遠離其原始位置。 因此,代碼更可能打印一些東西而不是崩潰。 在big-endian平台上,你的代碼會破壞指針的最重要的字節,因此將它瘋狂地指向一個完全不同的位置,從而使你的程序更容易崩潰。
我花了一段時間來理解它,但我想我終於明白了。
C ++標准指定reinterpret_cast<U&>(t)
等同於*reinterpret_cast<U*>(&t)
。
在我們的例子中, U
是char
, t
是char*
。
擴展這些,我們看到發生以下情況:
char**
類型的值。 reinterpret_cast
為char*
char
值。 reinterpret_cast
允許您從任何指針類型轉換為任何其他指針類型。 因此,從char**
到char*
的演員char**
很好。
我將嘗試使用我對引用和指針的根深蒂固的直覺來解釋這一點,而不是依賴於標准的語言。
那么,從這個角度來看,什么是“參考”? 或者更確切地說,您將如何“實施”參考? 當然,用指針。 因此,無論何時在某些代碼中看到引用,您都可以假裝它只是一個以特殊方式使用的指針:if int x;
和int& y{x};
然后我們真的有一個int* y_ptr = &x
; 如果我們說y = 123;
我們只是說*(y_ptr) = 123;
。 當我們使用C數組下標( a[1] = 2;
)時,實際發生的事情是a
被“衰減”表示指向其第一個元素的指針,然后執行的是*(a + 1) = 2
。
(旁注:編譯器實際上並不總是在每個引用后面都有指針;例如,編譯器可能會使用寄存器來引用變量,然后指針不能指向它。但這個比喻仍然非常安全。)
接受了“引用實際上只是偽裝的指針”這個比喻,現在我們可以用reinterpret_cast<>()
來忽略這個偽裝,這一點不足為奇。
PS - std::ref
在你深入研究時也只是一個指針。
當你使用C風格的演員表或使用reinterpret_cast進行投射時,你基本上是在告訴編譯器看另一種方式(“你不介意,我知道我在做什么”)。
C ++允許您告訴編譯器這樣做。 這並不意味着這是一個好主意......
它是允許的,因為C ++在你投射時幾乎可以提供任何東西。
但至於行為:
由於指針的最后一個字節被修改,新的內存地址就在附近,因此不太可能存在於您的程序不允許訪問的內存塊中。 這就是為什么你沒有得到seg-fault。 獲得的實際值是未定義的,但很可能是零,這解釋了將其解釋為char時的空白輸出。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.