C語言強制轉換與reinterpret_cast 轉換


C語言強制轉換等同於reinterpret_cast

 

reinterpret_cast 轉換

  通過重新解釋底層位模式在類型間轉換。

語法

 
reinterpret_cast < 新類型 > ( 表達式 )    
 

返回 新類型 類型的值。

解釋

與 static_cast 不同,但與 const_cast 類似,reinterpret_cast 表達式不會編譯成任何 CPU 指令(除非在整數和指針間轉換,或在指針表示依賴其類型的不明架構上)。它純粹是一個編譯時指令,指示編譯器將 表達式 視為如同具有 新類型 類型一樣處理。

只有下列轉換在不去除常量性易變性的場合才能用 reinterpret_cast 執行。

1) 整型、枚舉、指針或成員指針類型的表達式可轉換到其自身的類型。產生的值與 表達式 的相同。(C++11 起)
2) 指針能轉換成大小足以保有其類型所有值的任何整型類型(例如轉換成 std::uintptr_t)
3) 任何整型或枚舉類型的值可轉換到指針類型。指針轉換到有足夠大小的整數再轉換回同一指針類型后,保證擁有其原值,否則結果指針無法安全地解引用(不保證相反方向的往返轉換;相同指針可擁有多種整數表示)。不保證空指針常量 NULL 或整數零生成目標類型的空指針值;此時應該用 static_cast 或隱式轉換。
4) 任何 std::nullptr_t 類型的值,包含 nullptr,可轉換成任何整型類型,如同它是 (void*)0 一樣。但沒有值能轉換成 std::nullptr_t,甚至 nullptr 也不行:此時應該用 static_cast。(C++11 起)
5) 任何對象指針類型 T1* 可轉換成指向對象指針類型 cv T2* 。這嚴格等價於 static_cast<cv T2*>(static_cast<cv void*>(表達式))(這意味着,若 T2 的對齊要求不比 T1 的更嚴格,則指針值不改變,且將結果指針轉換回原類型將生成其原值)。任何情況下,只有類型別名化(type aliasing)規則允許(見下文)時,結果指針才可以安全地解引用。
6) T1 類型的左值表達式可轉換成到另一個類型 T2 的引用。結果是與原左值指代同一對象,但有不同類型的左值或亡值。不創建臨時量,不進行復制,不調用構造函數或轉換函數。只有類型別名化(type aliasing)規則允許(見下文)時,結果引用才可以安全訪問。
7) 任何函數指針可轉換成指向不同函數類型的指針。通過指向不同函數類型的指針調用函數是未定義的,但將這種指針轉換回指向原函數類型的指針將生成指向原函數的指針值。
8) 一些實現上(特別是在任何 POSIX 兼容的系統上,即基於 dlsym 的要求),函數指針可以轉換成 void* 或任何其他對象指針,反之亦然。若實現支持雙向的轉換,則轉換回原類型將生成原值,否則結果指針不能安全地解引用或調用。
9) 任何指針類型的空指針值可轉換成任何其他指針類型,產生該類型的空指針值。注意不能用 reinterpret_cast 將空指針常量 nullptr 或任何其他 std::nullptr_t 類型的值轉換為指針:此時應該使用隱式轉換或 static_cast。
10) 成員函數指針可轉換成指向不同類型的不同成員函數的指針。轉換回原類型將生成原值,否則結果指針不能安全使用。
11) 指向某類 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM