簡體   English   中英

為什么允許將指針強制轉換為引用?

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

在我們的例子中, Uchartchar*

擴展這些,我們看到發生以下情況:

  • 我們將參數的地址轉換為強制轉換,產生char**類型的值。
  • 我們將此值reinterpret_castchar*
  • 我們取消引用結果,產生一個char值。

reinterpret_cast允許您從任何指針類型轉換為任何其他指針類型。 因此,從char**char*的演員char**很好。

我將嘗試使用我對引用和指針的根深蒂固的直覺來解釋這一點,而不是依賴於標准的語言。

  • C沒有引用類型,它只有值和指針,
    因為,在物理上在內存中,我們只有值和指針。
  • 在C ++中,我們添加了對語法的引用,但您可以將它們視為一種語法糖 - 沒有用於保存引用的特殊數據結構或內存布局方案。

那么,從這個角度來看,什么是“參考”? 或者更確切地說,您將如何“實施”參考? 當然,用指針。 因此,無論何時在某些代碼中看到引用,您都可以假裝它只是一個以特殊方式使用的指針: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 ++在你投射時幾乎可以提供任何東西。

但至於行為:

  • pc是一個4字節的指針
  • (char)pc嘗試將指針解釋為一個字節,特別是四個字節中的最后一個
  • (char&)pc是相同的,但返回對該字節的引用
  • 當你第一次打印電腦時,沒有發生任何事情,你看到你存儲的信件
  • c ='B'修改4字節指針的最后一個字節,所以它現在指向別的東西
  • 當您再次打印時,您現在指向一個不同的位置來解釋您的結果。

由於指針的最后一個字節被修改,新的內存地址就在附近,因此不太可能存在於您的程序不允許訪問的內存塊中。 這就是為什么你沒有得到seg-fault。 獲得的實際值是未定義的,但很可能是零,這解釋了將其解釋為char時的空白輸出。

暫無
暫無

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

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