ควรใช้ reinterpret_cast เมื่อใด


459

ฉันกำลังสับสนเล็กน้อยกับการบังคับใช้ของVSreinterpret_cast จากสิ่งที่ฉันได้อ่านกฎทั่วไปที่ใช้งานหล่อแบบคงที่เมื่อชนิดสามารถตีความที่รวบรวมเวลาด้วยเหตุนี้คำว่าstatic_cast staticนี่คือ cast คอมไพเลอร์ C ++ ที่ใช้ภายในสำหรับการร่ายโดยปริยายด้วย

reinterpret_casts ใช้งานได้ในสองสถานการณ์:

  • แปลงประเภทจำนวนเต็มเป็นประเภทของตัวชี้และในทางกลับกัน
  • แปลงประเภทตัวชี้หนึ่งเป็นประเภทอื่น แนวคิดทั่วไปที่ฉันได้รับคือสิ่งนี้ไม่สามารถพูดได้และควรหลีกเลี่ยง

ฉันอยู่ที่ไหนน้อยสับสนเป็นหนึ่งในการใช้งานที่ฉันต้องการฉันโทร c ++ จากซีและความต้องการรหัส C จะยึดมั่นในการ C ++ void*วัตถุดังนั้นโดยทั่วไปมันถือ คาสต์ใดที่ควรใช้เพื่อแปลงระหว่างvoid *และประเภทคลาส

ฉันได้เห็นการใช้งานของทั้งสองstatic_castและreinterpret_cast? แม้ว่าจากสิ่งที่ฉันได้อ่านดูเหมือนstaticจะดีกว่าที่นักแสดงสามารถเกิดขึ้นได้ในเวลารวบรวม? แม้ว่ามันจะบอกว่าจะใช้reinterpret_castในการแปลงจากประเภทตัวชี้หนึ่งไปสู่อีกประเภทหนึ่ง?


9
reinterpret_castไม่ได้เกิดขึ้นในเวลาทำงาน พวกเขาทั้งสองงบรวบรวมเวลา จากen.cppreference.com/w/cpp/language/reinterpret_cast : "ซึ่งแตกต่างจาก static_cast แต่เช่น const_cast นิพจน์ reinterpret_cast ไม่ได้รวบรวมคำสั่ง CPU ใด ๆ มันเป็นคำสั่งของคอมไพเลอร์อย่างแท้จริงซึ่งสั่งให้คอมไพเลอร์รักษาลำดับของบิต (การแทนวัตถุ) ของการแสดงออกราวกับว่ามันมีประเภท new_type "
Cris Luengo

@HeretoLearn เป็นไปได้หรือไม่ที่จะเพิ่มส่วนรหัสที่เกี่ยวข้องจากไฟล์ * .c และ * .cpp ฉันคิดว่ามันสามารถปรับปรุงการแสดงออกของคำถาม
OrenIshShalom

คำตอบ:


442

มาตรฐาน C ++ รับประกันดังต่อไปนี้:

static_castไอเอ็นจีตัวชี้ไปยังและจากการvoid*รักษาที่อยู่ นั่นคือในต่อไปนี้a, bและcทุกจุดที่อยู่เดียวกัน:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_castรับประกันได้ว่าหากคุณชี้ตัวชี้ไปยังประเภทอื่นแล้วreinterpret_castกลับไปเป็นประเภทดั้งเดิมคุณจะได้รับค่าดั้งเดิม ดังนั้นในต่อไปนี้:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

aและcมีค่าเหมือนกัน แต่ค่าของการbเป็นพลรบ (โดยทั่วไปจะมีที่อยู่เดียวกับaและcแต่ไม่ได้ระบุไว้ในมาตรฐานและอาจไม่เป็นจริงในเครื่องที่มีระบบหน่วยความจำที่ซับซ้อนมากขึ้น)

สำหรับการหล่อไปและกลับจากvoid*, static_castควรได้รับการแนะนำ


18
ฉันชอบความจริงที่ว่า 'b' ไม่ได้ถูกกำหนด มันหยุดคุณทำสิ่งที่โง่ด้วย หากคุณส่งบางสิ่งไปยังตัวชี้ประเภทอื่นคุณกำลังถามถึงปัญหาและความจริงที่ว่าคุณไม่สามารถพึ่งพามันได้จะทำให้คุณระมัดระวังมากขึ้น หากคุณใช้ static_cast <> ด้านบนการใช้ 'b' คืออะไร
Martin York

3
ฉันคิดว่า reinterpret_cast <> รับประกันรูปแบบบิตเดียวกัน (ซึ่งไม่เหมือนกับตัวชี้ที่ถูกต้องสำหรับประเภทอื่น)
Martin York

37
ค่าของbไม่ได้อีกต่อไปที่ไม่ได้ระบุใน C ++ 11 reinterpret_castเมื่อใช้ และใน C ++ 03 โยนของint*ที่จะvoid*ถูกห้ามไม่ให้ทำได้ด้วยreinterpret_cast(แม้ว่าคอมไพเลอร์ไม่ได้ดำเนินการตามนั้นและมันก็ทำไม่ได้จึงได้มีการเปลี่ยนแปลงสำหรับ C ++ 11)
Johannes Schaub - litb

55
สิ่งนี้ไม่ตอบคำถาม "เมื่อใดที่จะใช้ reinterpret_cast"
einpoklum

6
@ LokiAstari ฉันคิดว่าไม่ได้ระบุไม่ได้หยุดคุณจากการทำสิ่งที่โง่ มันจะหยุดคุณเมื่อคุณจำได้ว่ามันไม่ได้ระบุ ความแตกต่างอย่างมาก ส่วนตัวฉันไม่ชอบไม่ได้ระบุ จำมากเกินไป
Helin Wang

158

กรณีหนึ่งที่reinterpret_castจำเป็นเมื่อมีการเชื่อมต่อกับชนิดข้อมูลทึบแสง สิ่งนี้เกิดขึ้นบ่อยครั้งใน API ของผู้ขายซึ่งโปรแกรมเมอร์ไม่สามารถควบคุมได้ ต่อไปนี้เป็นตัวอย่างที่วางแผนไว้โดยที่ผู้ขายได้จัดทำ API สำหรับการจัดเก็บและเรียกใช้ข้อมูลส่วนกลางโดยพลการ:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

ในการใช้ API นี้โปรแกรมเมอร์จะต้องส่งข้อมูลไปVendorGlobalUserDataและกลับมาอีกครั้ง static_castจะไม่ทำงานต้องใช้reinterpret_cast:

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

ด้านล่างนี้เป็นการใช้งาน API ตัวอย่างที่วางแผนไว้:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

7
ใช่นั่นเป็นเพียงการใช้ reinterpret_cast ที่มีความหมายที่ฉันสามารถนึกได้เท่านั้น
jalf

8
นี่อาจเป็นคำถามสุดท้าย แต่ทำไม API ของผู้ขายจึงไม่ใช้void*สิ่งนั้น
Xeo

19
@Xeo พวกเขาไม่ได้ใช้โมฆะ * เพราะพวกเขาเสียการตรวจสอบประเภท (บางส่วน) ในเวลารวบรวม
jesup

4
กรณีการใช้งานจริงของชนิดข้อมูล "ทึบแสง" คือเมื่อคุณต้องการแสดง API เป็น C แต่เขียนการใช้งานใน C ++ ห้องไอซียูเป็นตัวอย่างของห้องสมุดที่ทำสิ่งนี้ในหลาย ๆ ที่ ตัวอย่างเช่นใน API ตัวตรวจสอบการปลอมแปลงคุณจัดการกับพอยน์เตอร์ของประเภทUSpoofChecker*โดยที่USpoofCheckerstruct ว่างเปล่า อย่างไรก็ตามภายใต้ประทุนเมื่อใดก็ตามที่คุณผ่าน a USpoofChecker*มันจะผ่านreinterpret_castไปยังชนิด C ++ ภายใน
sffc

@sffc ทำไมไม่เปิดเผยประเภท C struct ให้กับผู้ใช้?
Gupta

101

คำตอบสั้น ๆ : ถ้าคุณไม่รู้ว่าอะไรreinterpret_castคือสิ่งที่ควรทำอย่าใช้มัน หากคุณต้องการในอนาคตคุณจะรู้

คำตอบแบบเต็ม:

ลองพิจารณาประเภทหมายเลขพื้นฐาน

เมื่อคุณแปลงตัวอย่างint(12)ที่จะunsigned float (12.0f)ตอบสนองความต้องการประมวลผลของคุณจะก่อให้เกิดการคำนวณบางส่วนเป็นตัวเลขทั้งมีการแสดงแตกต่างกันเล็กน้อย นี่คือสิ่งที่static_castหมายถึง

ในทางกลับกันเมื่อคุณเรียกreinterpret_castใช้ CPU จะไม่เรียกใช้การคำนวณใด ๆ มันแค่ตั้งค่าบิตในหน่วยความจำเช่นถ้ามันมีประเภทอื่น ดังนั้นเมื่อคุณแปลงint*เป็นfloat*คำหลักนี้ค่าใหม่ (หลังจากตัวชี้การยกเลิกการลงทะเบียน) จะไม่เกี่ยวข้องกับค่าเก่าในความหมายทางคณิตศาสตร์

ตัวอย่าง:มันเป็นความจริงที่reinterpret_castไม่สามารถพกพาได้เนื่องจากเหตุผลหนึ่งข้อ - สั่งไบต์ (endianness) แต่นี่เป็นเหตุผลที่ดีที่สุดที่จะใช้ ลองนึกภาพตัวอย่าง: คุณต้องอ่านเลขฐานสอง 32 บิตจากไฟล์และคุณรู้ว่ามันเป็น endian ที่ยิ่งใหญ่ รหัสของคุณจะต้องเป็นแบบทั่วไปและทำงานอย่างถูกต้องในระบบ endian ขนาดใหญ่ (เช่น ARM บางตัว) และระบบ endian ขนาดเล็ก (เช่น x86) ดังนั้นคุณต้องตรวจสอบลำดับไบต์ มันเป็นที่รู้จักกันดีในเวลารวบรวมเพื่อให้คุณสามารถเขียนconstexprฟังก์ชั่น:คุณสามารถเขียนฟังก์ชั่นเพื่อให้บรรลุนี้:

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

คำอธิบาย:การเป็นตัวแทนไบนารีxในหน่วยความจำอาจเป็น0000'0000'0000'0001(ใหญ่) หรือ0000'0001'0000'0000(endian น้อย) หลังจากแปลหล่อไบต์ภายใต้pตัวชี้อาจเป็นตามลำดับหรือ0000'0000 0000'0001หากคุณใช้การหล่อแบบคงที่จะเกิดขึ้นเสมอ0000'0001ไม่ว่าจะใช้งานแบบ endianness ใดก็ตาม

แก้ไข:

ในรุ่นแรกที่ผมทำเช่นฟังก์ชั่นที่จะเป็นis_little_endian constexprมันรวบรวมได้ดีใน gcc ใหม่ล่าสุด (8.3.0) แต่มาตรฐานบอกว่าผิดกฎหมาย คอมไพเลอร์เสียงดังกราวปฏิเสธที่จะรวบรวมมัน (ซึ่งถูกต้อง)


1
เป็นตัวอย่างที่ดี! ฉันจะแทนที่ short สำหรับ uint16_t และ char ที่ไม่ได้ลงชื่อสำหรับ uint8_t เพื่อให้คลุมเครือน้อยลงสำหรับมนุษย์
Jan Turoň

@ JanTuroňจริงเราไม่สามารถสรุปได้ว่าshortใช้หน่วยความจำ 16 บิต การแก้ไข
jaskmar

1
ตัวอย่างไม่ถูกต้อง ไม่อนุญาตให้ reinterpret_cast ในฟังก์ชั่น constexpr
Michael Veksler

1
ก่อนอื่นรหัสนี้ถูกปฏิเสธทั้งเสียงดังกราวล่าสุด (7.0.0) และ gcc (8.2.0) น่าเสียดายที่ฉันไม่พบข้อ จำกัด ในภาษาที่เป็นทางการ ทั้งหมดที่ฉันหาได้คือsocial.msdn.microsoft.com/Forums/vstudio/en-US/…
Michael Veksler

2
โดยเฉพาะอย่างยิ่งen.cppreference.com/w/cpp/language/constant_expression (รายการที่ 16) ระบุไว้อย่างชัดเจนว่า reinterpret_cast ไม่สามารถใช้ในนิพจน์คงที่ได้ ดูที่github.com/cplusplus/draft/blob/master/papers/N3797.pdf (5.19 นิพจน์คงที่) หน้า 125-126 ซึ่งออกกฎ reinterpret_cast อย่างชัดเจน 7.1.5จากนั้นตัวระบุ constexprรายการ 5 (หน้า 146) * สำหรับฟังก์ชัน constexpr ที่ไม่ใช่ค่าเริ่มต้น ... หากไม่มีค่าอาร์กิวเมนต์ที่มีอยู่เช่นนั้น ... อาจเป็นนิพจน์ย่อยหลักที่ประเมินค่าได้ (5.19 ) โปรแกรมดังกล่าวเกิดขึ้นไม่ได้ *
Michael Veksler

20

ความหมายของreinterpret_castไม่ได้ถูกกำหนดโดยมาตรฐาน C ++ ดังนั้นในทางทฤษฎีแล้วreinterpret_castอาจทำให้โปรแกรมของคุณพังได้ ในทางปฏิบัติคอมไพเลอร์พยายามที่จะทำสิ่งที่คุณคาดหวังซึ่งก็คือการตีความบิตของสิ่งที่คุณกำลังส่งผ่านราวกับว่าพวกเขาเป็นประเภทที่คุณหล่อ ถ้าคุณรู้ว่าคอมไพเลอร์ที่คุณจะใช้ทำอะไรกับreinterpret_cast คุณสามารถใช้มัน แต่จะบอกว่ามันเป็นแบบพกพาที่จะโกหก

สำหรับกรณีที่คุณอธิบายและกรณีใด ๆ ที่คุณอาจพิจารณาreinterpret_castคุณสามารถใช้static_castหรือทางเลือกอื่นแทน เหนือสิ่งอื่นใดมาตรฐานได้กล่าวถึงสิ่งที่คุณคาดหวังได้static_cast(§5.2.9):

ค่า rvalue ของชนิด“ pointer to cv void” สามารถแปลงเป็นตัวชี้ไปยังชนิดของวัตถุได้อย่างชัดเจน ค่าประเภทตัวชี้ไปยังวัตถุที่แปลงเป็น“ ตัวชี้ไปยังช่องว่าง” และกลับไปเป็นประเภทตัวชี้ดั้งเดิมจะมีค่าเดิม

static_castดังนั้นสำหรับกรณีการใช้งานของคุณดูเหมือนว่าค่อนข้างชัดเจนว่าคณะกรรมการมาตรฐานที่มีไว้สำหรับคุณที่จะใช้


5
ไม่ผิดพลาดมากโปรแกรมของคุณ มาตรฐานมีการรับประกันเล็กน้อยเกี่ยวกับ reinterpret_cast เพียงไม่มากเท่าที่คนมักจะคาดหวัง
jalf

1
ไม่ใช่ถ้าคุณใช้มันอย่างถูกต้อง นั่นคือ reinterpret_cast จาก A ถึง B ถึง A นั้นปลอดภัยอย่างสมบูรณ์และถูกต้อง แต่คุณค่าของ B นั้นไม่ได้ระบุและใช่ถ้าคุณพึ่งพาสิ่งนั้นสิ่งเลวร้ายอาจเกิดขึ้นได้ แต่ตัวละครนั้นปลอดภัยพอตราบใดที่คุณใช้มันในแบบที่มาตรฐานอนุญาตเท่านั้น ;)
jalf

55
ฮ่า ๆ ฉันสงสัยว่า reinterpret_crash อาจทำให้โปรแกรมของคุณพังจริงๆ แต่ reinterpret_cast จะไม่ทำเช่นนั้น ;)
jalf

5
<ประชด> reinterpret_crashฉันพยายามบนคอมไพเลอร์ของฉันและอย่างใดก็ปฏิเสธที่จะรวบรวม ไม่มีทางที่บั๊กของคอมไพเลอร์จะหยุดยั้งฉันจากการขัดจังหวะโปรแกรม reinterpreting ของฉัน ฉันจะรายงานข้อผิดพลาดโดยเร็ว </irony>
paercebal

18
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }

12

การใช้ reinterpret_cast ครั้งเดียวคือหากคุณต้องการใช้การดำเนินการระดับบิตกับลอย (IEEE 754) ตัวอย่างหนึ่งของสิ่งนี้คือเคล็ดลับ Square-Root ที่ตรงกันข้าม:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

มันทำหน้าที่เป็นตัวแทนไบนารีของการลอยเป็นจำนวนเต็มเลื่อนไปทางขวาและลบมันออกจากค่าคงที่จึงลดลงครึ่งหนึ่งและลบล้างเลขชี้กำลัง หลังจากแปลงกลับเป็นทุ่นก็ต้องผ่านการทำซ้ำของ Newton-Raphson เพื่อให้การประมาณนี้แม่นยำยิ่งขึ้น:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the deuce? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

สิ่งนี้เขียนขึ้นเป็นภาษา C ดังนั้นใช้ C casts แต่การใช้ C ++ แบบอะนาล็อกคือ reinterpret_cast


1
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))- ideone.com/6S4ijc
Orwellophile

1
มาตรฐานบอกว่านี่เป็นพฤติกรรมที่ไม่ได้กำหนด: en.cppreference.com/w/cpp/language/reinterpret_cast (ภายใต้ "ประเภท aliasing")
Cris Luengo

@CrisLuengo ถ้าฉันแทนที่ทั้งหมดreinterpret_castด้วยmemcpyจะเป็น UB หรือไม่
sandthorn

@sandthorn: นี่คือ UB ตามมาตรฐาน แต่ถ้ามันเหมาะกับสถาปัตยกรรมของคุณไม่ต้องกังวลกับมัน เคล็ดลับนี้ก็โอเคฉันคิดว่าสำหรับคอมไพเลอร์ใด ๆ สำหรับสถาปัตยกรรมของ Intel มันไม่สามารถทำงานได้ตามที่ตั้งใจ (หรือผิดพลาด) ในสถาปัตยกรรมอื่น ๆ - ตัวอย่างเช่นอาจเป็นไปได้ว่าการลอยและยาวถูกเก็บไว้ในช่องหน่วยความจำแยกต่างหาก (ไม่ใช่ที่ฉันรู้สถาปัตยกรรมดังกล่าวเป็นเพียงอาร์กิวเมนต์ ... ) . memcpyจะทำให้ถูกกฎหมายอย่างแน่นอน
Cris Luengo


2
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

ฉันพยายามที่จะสรุปและเขียน cast ปลอดภัยอย่างง่ายโดยใช้แม่แบบ โปรดทราบว่าวิธีนี้ไม่รับประกันว่าจะใช้งานพอยน์เตอร์ในฟังก์ชั่น


1
อะไร? ทำไมต้องรำคาญ นี่คือสิ่งที่เกิดขึ้นreinterpret_castแล้วในสถานการณ์นี้: "ตัวชี้วัตถุสามารถแปลงเป็นตัวชี้วัตถุที่เป็นประเภทต่าง ๆ ได้อย่างชัดเจน [72] เมื่อprvalue vของประเภทตัวชี้วัตถุถูกแปลงเป็นประเภทตัวชี้วัตถุ“ ตัวชี้ไปยังcv T ” ผลลัพธ์ก็คือstatic_cast<cv T*>(static_cast<cv void*>(v))" - N3797
underscore_d

ในฐานะที่เป็นc++2003มาตรฐานที่ฉันจะไม่พบว่าreinterpret_castไม่static_cast<cv T*>(static_cast<cv void*>(v))
Sasha Zezulinsky

1
ตกลงจริง แต่ฉันไม่สนใจรุ่นหนึ่งจากเมื่อ 13 ปีที่แล้วและไม่ควรมีรหัสส่วนใหญ่หาก (น่าจะเป็น) พวกเขาสามารถหลีกเลี่ยงได้ คำตอบและความคิดเห็นควรสะท้อนถึงมาตรฐานล่าสุดที่มีอยู่เว้นแต่จะระบุไว้เป็นอย่างอื่น ... IMHO อย่างไรก็ตามฉันเดาว่าคณะกรรมการรู้สึกจำเป็นที่จะต้องเพิ่มสิ่งนี้อย่างชัดเจนหลังจากปี 2003 (เนื่องจาก IIRC มันเหมือนกันใน C ++ 11)
underscore_d

ก่อนที่มันเป็นC++03 C++98ตันของโครงการใช้ C ++ เก่าแทนพกพา C บางครั้งคุณต้องใส่ใจกับการพกพา ตัวอย่างเช่นคุณต้องสนับสนุนรหัสเดียวกันบน Solaris, AIX, HPUX, Windows ในกรณีที่การพึ่งพาตัวแปลภาษาและการพกพาเป็นเรื่องที่ยุ่งยาก ตัวอย่างที่ดีของการแนะนำให้พกพาได้คือการใช้ a reinterpret_castในโค้ดของคุณ
Sasha Zezulinsky

อีกครั้งถ้าเช่นฉันคุณมีความสุขที่จะ จำกัด ตัวเองเฉพาะกับแพลตฟอร์มที่สนุกกับภาษารุ่นล่าสุดและยิ่งใหญ่ที่สุดการคัดค้านของคุณคือจุดที่สงสัย
underscore_d

1

ก่อนอื่นคุณมีข้อมูลบางประเภทเช่น int ที่นี่:

int x = 0x7fffffff://==nan in binary representation

จากนั้นคุณต้องการเข้าถึงตัวแปรเดียวกับประเภทอื่นเช่น float: คุณสามารถเลือกได้

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

หรือ

float y = *(float*)&(x);

//this could be used in c and cpp

โดยย่อ: มันหมายความว่าหน่วยความจำเดียวกันถูกใช้เป็นประเภทที่แตกต่างกัน ดังนั้นคุณสามารถแปลงการแทนเลขฐานสองของโฟลตเป็นชนิด int เช่นด้านบนเป็นโฟลต 0x80000000 เป็น -0 เช่น (mantissa และ exponent นั้นเป็นโมฆะ แต่เครื่องหมาย, msb เป็นหนึ่งสิ่งนี้ใช้ได้กับคู่ผสมและคู่ยาว

OPTIMIZE: ฉันคิดว่า reinterpret_cast จะได้รับการปรับให้เหมาะสมที่สุดในคอมไพเลอร์หลาย ๆ ตัวในขณะที่ c-casting นั้นทำขึ้นโดยตัวชี้เลขคณิต (ค่าจะต้องคัดลอกไปยังหน่วยความจำทำให้พอยน์เตอร์ชี้ไม่ได้

หมายเหตุ: ในทั้งสองกรณีคุณควรบันทึกค่า casted ในตัวแปรก่อนร่าย! มาโครนี้สามารถช่วย:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

มันเป็นความจริงว่า "มันหมายความว่าหน่วยความจำเดียวกันถูกใช้เป็นประเภทที่แตกต่างกัน" แต่ถูก จำกัด ประเภทคู่ที่เฉพาะเจาะจง ในreinterpret_castรูปแบบตัวอย่างของคุณintถึงfloat&คือพฤติกรรมที่ไม่ได้กำหนด
jaskmar

1

เหตุผลหนึ่งที่ใช้reinterpret_castคือเมื่อคลาสพื้นฐานไม่มี vtable แต่คลาสที่ได้รับมา ในกรณีนั้นstatic_castและreinterpret_castจะส่งผลให้มีค่าพอยน์เตอร์ที่แตกต่างกัน (นี่อาจเป็นกรณีที่ผิดปกติที่jalf ด้านบน ) เช่นเดียวกับข้อจำกัดความรับผิดชอบฉันไม่ได้ระบุว่านี่เป็นส่วนหนึ่งของมาตรฐาน แต่เป็นการใช้งานคอมไพเลอร์ที่แพร่หลายหลายตัว

ตัวอย่างใช้รหัสด้านล่าง:

#include <cstdio>

class A {
public:
    int i;
};

class B : public A {
public:
    virtual void func() {  }
};

int main()
{
    B b;
    const A* a = static_cast<A*>(&b);
    const A* ar = reinterpret_cast<A*>(&b);

    printf("&b = %p\n", &b);
    printf(" a = %p\n", a);
    printf("ar = %p\n", ar);
    printf("difference = %ld\n", (long int)(a - ar));

    return 0;
}

ซึ่งแสดงผลเช่น:

& b = 0x7ffe10e68b38
a = 0x7ffe10e68b40
ar = 0x7ffe10e68b38
ความแตกต่าง = 2

ในคอมไพเลอร์ทั้งหมดที่ฉันลองใช้ (MSVC 2015 และ 2017, เสียงดังกราว 8.0.0, gcc 9.2, icc 19.0.1 - ดูgodbolt ในช่วง 3 ) ผลลัพธ์ของความstatic_castแตกต่างจากที่reinterpret_cast2 (4 สำหรับ MSVC) คอมไพเลอร์เดียวที่จะเตือนเกี่ยวกับความแตกต่างคือเสียงดังกราวด้วย:

17:16: คำเตือน: 'reinterpret_cast' จากคลาส 'B *' ถึงฐานที่ออฟเซ็ตที่ไม่เป็นศูนย์ 'A *' จะทำงานแตกต่างจาก 'static_cast' [-Wreinterpret-base-class]
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~~~~~~~~
17:16: หมายเหตุ: ใช้ 'static_cast' เพื่อปรับตัวชี้อย่างถูกต้องในขณะที่ upcasting
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~~
static_cast

หนึ่งข้อแม้สุดท้ายคือถ้าชั้นฐานไม่มีสมาชิกข้อมูล (เช่นint i;) ดังนั้นเสียงดังกราว, gcc และ icc ส่งกลับที่อยู่เดียวกันสำหรับreinterpret_castในstatic_castขณะที่ MSVC ยังไม่


1

นี่คือความแตกต่างของโปรแกรมของ Avi Ginsburg ซึ่งแสดงให้เห็นอย่างชัดเจนถึงคุณสมบัติของreinterpret_castChris Luengo, flodin และ cmdLP ที่คอมไพเลอร์ปฏิบัติต่อตำแหน่งหน่วยความจำแบบแหลมถึงราวกับว่ามันเป็นวัตถุประเภทใหม่:

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class A
{
public:
    int i;
};

class B : public A
{
public:
    virtual void f() {}
};

int main()
{
    string s;
    B b;
    b.i = 0;
    A* as = static_cast<A*>(&b);
    A* ar = reinterpret_cast<A*>(&b);
    B* c = reinterpret_cast<B*>(ar);

    cout << "as->i = " << hex << setfill('0')  << as->i << "\n";
    cout << "ar->i = " << ar->i << "\n";
    cout << "b.i   = " << b.i << "\n";
    cout << "c->i  = " << c->i << "\n";
    cout << "\n";
    cout << "&(as->i) = " << &(as->i) << "\n";
    cout << "&(ar->i) = " << &(ar->i) << "\n";
    cout << "&(b.i) = " << &(b.i) << "\n";
    cout << "&(c->i) = " << &(c->i) << "\n";
    cout << "\n";
    cout << "&b = " << &b << "\n";
    cout << "as = " << as << "\n";
    cout << "ar = " << ar << "\n";
    cout << "c  = " << c  << "\n";

    cout << "Press ENTER to exit.\n";
    getline(cin,s);
}

ซึ่งผลลัพธ์ในผลลัพธ์เช่นนี้:

as->i = 0
ar->i = 50ee64
b.i   = 0
c->i  = 0

&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i) = 00EFF978
&(c->i) = 00EFF978

&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c  = 00EFF974
Press ENTER to exit.

จะเห็นได้ว่าวัตถุ B นั้นสร้างขึ้นในหน่วยความจำเป็นข้อมูลเฉพาะ B ก่อนตามด้วยวัตถุ A ที่ฝังตัว การstatic_castส่งคืนที่อยู่ของวัตถุ A ที่static_castถูกต้องและตัวชี้ที่สร้างขึ้นอย่างถูกต้องจะให้ค่าของเขตข้อมูล ตัวชี้ที่สร้างขึ้นโดยreinterpret_castใช้bหน่วยความจำของสถานที่ตั้งราวกับว่ามันเป็นวัตถุ A ธรรมดาและเมื่อตัวชี้พยายามรับเขตข้อมูลมันจะส่งคืนข้อมูลเฉพาะ B บางอย่างราวกับว่าเป็นเนื้อหาของเขตข้อมูลนี้

การใช้หนึ่งreinterpret_castคือการแปลงตัวชี้เป็นจำนวนเต็มที่ไม่ได้ลงชื่อ (เมื่อพอยน์เตอร์และจำนวนเต็มที่ไม่ได้ลงชื่อมีขนาดเท่ากัน):

int i; unsigned int u = reinterpret_cast<unsigned int>(&i);



-16

อ่านคำถามที่พบบ่อย ! การเก็บข้อมูล C ++ ใน C อาจมีความเสี่ยง

ใน C ++ ตัวชี้ไปยังวัตถุสามารถแปลงเป็นvoid *โดยไม่ต้องปลดเปลื้องใด ๆ แต่มันไม่เป็นความจริงเลย คุณต้องการ a static_castเพื่อให้ได้ตัวชี้ดั้งเดิมกลับมา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.