C語言強制轉換等同於reinterpret_cast
reinterpret_cast 轉換
通過重新解釋底層位模式在類型間轉換。
語法
reinterpret_cast < 新類型 > ( 表達式 ) |
|||||||||
返回 新類型 類型的值。
解釋
與 static_cast 不同,但與 const_cast 類似,reinterpret_cast 表達式不會編譯成任何 CPU 指令(除非在整數和指針間轉換,或在指針表示依賴其類型的不明架構上)。它純粹是一個編譯時指令,指示編譯器將 表達式 視為如同具有 新類型 類型一樣處理。
只有下列轉換在不去除常量性或易變性的場合才能用 reinterpret_cast 執行。
T1*
可轉換成指向對象指針類型 cv T2*
。這嚴格等價於 static_cast<cv T2*>(static_cast<cv void*>(表達式))(這意味着,若 T2
的對齊要求不比 T1
的更嚴格,則指針值不改變,且將結果指針轉換回原類型將生成其原值)。任何情況下,只有類型別名化(type aliasing)規則允許(見下文)時,結果指針才可以安全地解引用。
T1
類型的左值表達式可轉換成到另一個類型 T2
的引用。結果是與原左值指代同一對象,但有不同類型的左值或亡值。不創建臨時量,不進行復制,不調用構造函數或轉換函數。只有類型別名化(type aliasing)規則允許(見下文)時,結果引用才可以安全訪問。
T1
的成員對象的指針可轉換成指向另一個類 T2
的另一個成員對象的指針。若 T2
的對齊不比 T1
更嚴格,則轉換回原類型 T1
將生成原值,否則不能安全地使用結果指針。
同所有轉型表達式,結果是:
- 左值,如果 新類型 是左值引用或到函數類型的右值引用;
- 亡值,如果 新類型 是到對象類型的右值引用;
- 否則為純右值。
類型別名化
如果下列條件均不滿足,每當試圖通過 別名類型
類型的泛左值讀或修改類型為 動態類型
的對象的值時,行為未定義:
別名類型
與動態類型
相似。別名類型
是動態類型
的(可有 cv 限定的)有符號或無符號變體。別名類型
為 std::byte、 (C++17 起)char 或 unsigned char:這容許將任何對象的對象表示作為一個字節數組加以檢驗。
非正式地說,忽略頂層 cv 限定性,若兩個類型符合下列條件,則它們相似:
- 它們是同一類型;或
- 它們都是指針,且被指向的類型相似;或
- 它們都是指向相同類的成員指針,且被指向的成員類型相似;或
|
(C++20 前) |
|
(C++20 起) |
例如:
- const int * volatile * 與 int * * const 相似;
- const int (* volatile S::* const)[20] 與 int (* const S::* volatile)[20] 相似;
- int (* const *)(int *) 與 int (* volatile *)(int *) 相似;
- int (S::*)() const 與 int (S::*)() 不相似;
- int (*)(int *) 與 int (*)(const int *) 不相似;
- const int (*)(int *) 與 int (*)(int *) 不相似;
- int (*)(int * const) 與 int (*)(int *) 相似(它們是同一類型);
- std::pair<int, int> 與 std::pair<const int, int> 不相似。
此規則允許進行基於類型的別名分析,即編譯器假設通過一個類型的泛左值讀取的值,不會被通過不同類型的泛左值的寫入所修改(依據上述例外情況)。
注意,許多 C++ 編譯器作為非標准語言擴展放松此規則,以允許通過 union 的不活躍成員的進行類型錯誤的訪問(這種訪問在 C 中並不是未定義的)。
注解
標准中定義嚴格別名化規則的段落含有兩條額外條例,部分從 C 繼承而來:
別名類型
為聚合類型或 union 類型,它保有前述各類型之一作為其元素或非靜態成員(遞歸地包含子聚合體的元素和被包含的聯合體的非靜態數據成員)。別名類型
為動態類型
的(可有 cv 限定的)基類。
這些條例所描述的情況不可能出現於 C++,從而從上面的討論中省略。在 C 中,聚合復制和賦值將聚合體對象作為整體訪問。但 C++ 中始終通過成員函數調用進行這種行動,這會訪問單獨的子對象而非整個對象(或在聯合體的情況下,復制對象表示,即經由 unsigned char)。
假設符合對齊要求,則 reinterpret_cast
在處理指針可互轉換對象的少數受限情況外,不更改指針的值:
struct S1 { int a; } s1; struct S2 { int a; private: int b; } s2; // 非標准布局 union U { int a; double b; } u = {0}; int arr[2]; int* p1 = reinterpret_cast<int*>(&s1); // p1 的值為“指向 s1.a 的指針” // 因為 s1.a 與 s1 為指針可互轉換 int* p2 = reinterpret_cast<int*>(&s2); // reinterpret_cast 不更改 p2 的值為“指向 s2 的指針”。 int* p3 = reinterpret_cast<int*>(&u); // p3 的值為“指向 u.a 的指針”:u.a 與 u 指針可互轉換 double* p4 = reinterpret_cast<double*>(p3); // p4 的指針為“指向 u.b 的指針”:u.a 與 u.b // 指針可互轉換,因為都與 u 指針可互轉換 int* p5 = reinterpret_cast<int*>(&arr); // reinterpret_cast 不更改 p5 的值為“指向 arr 的指針”
在不實際代表適當類型的對象的泛左值(例如通過 reinterpret_cast
所獲得)上,進行代表非靜態數據成員或非靜態成員函數的成員訪問,將導致未定義行為:
struct S { int x; }; struct T { int x; int f(); }; struct S1 : S {}; // 標准布局 struct ST : S, T {}; // 非標准布局 S s = {}; auto p = reinterpret_cast<T*>(&s); // p 的值為“指向 s 的指針” auto i = p->x; // 類成員訪問表達式為未定義行為:s 不是 T 對象 p->x = 1; // 未定義行為 p->f(); // 未定義行為 S1 s1 = {}; auto p1 = reinterpret_cast<S*>(&s1); // p1 的值為“指向 S 的 s1 子對象的指針” auto i = p1->x; // OK p1->x = 1; // OK ST st = {}; auto p2 = reinterpret_cast<S*>(&st); // p2 的值為“指向 st 的指針” auto i = p2->x; // 未定義行為 p2->x = 1; // 未定義行為
許多編譯器在這種情況下發布“嚴格別名化”警告,即使在技術上這種構造所違背的並非稱為“嚴格別名化規則”段落的規則。
嚴格別名化及其相關規則的目的,是啟用基於類型的別名分析,若程序能合法地創建一種情形,使得兩個指向無關類型的指針(例如一個 int* 和一個 float*)能同時存在並可一同用於加載或存儲同一內存,則別名分析會普遍無效。故任何看起來能夠創建這種情形的技巧都必然導致未定義行為。
當需要將對象的字節解釋為不同類型的值時,可以使用 std::memcpy 或 std::bit_cast (C++20 起):
double d = 0.1; std::int64_t n; static_assert(sizeof n == sizeof d); // n = *reinterpret_cast<std::int64_t*>(&d); // 未定義行為 std::memcpy(&n, &d, sizeof d); // OK n = std::bit_cast<std::int64_t>(d); // 亦 OK
示例
演示 reinterpret_cast 的一些用法:
#include <cstdint> #include <cassert> #include <iostream> int f() { return 42; } int main() { int i = 7; // 指針到整數並轉回 std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // 不能誤用 static_cast std::cout << "&i 的值是 0x" << std::hex << v1 << '\n'; int* p1 = reinterpret_cast<int*>(v1); assert(p1 == &i); // 到另一函數指針並轉回 void(*fp1)() = reinterpret_cast<void(*)()>(f); // fp1(); 未定義行為 int(*fp2)() = reinterpret_cast<int(*)()>(fp1); std::cout << std::dec << fp2() << '\n'; // 安全 // 通過指針的類型別名化 char* p2 = reinterpret_cast<char*>(&i); if(p2[0] == '\x7') std::cout << "本系統是小端的\n"; else std::cout << "本系統是大端的\n"; // 通過引用的類型別名化 reinterpret_cast<unsigned int&>(i) = 42; std::cout << i << '\n'; [[maybe_unused]] const int &const_iref = i; // int &iref = reinterpret_cast<int&>(const_iref); // 編譯錯誤——不能去除 const // 必須用 const_cast 代替:int &iref = const_cast<int&>(const_iref); }
可能的輸出:
&i 的值是 0x7fff352c3580 42 本系統是小端的 42