อะไรคือการใช้งานที่เหมาะสมของ:
static_castdynamic_castconst_castreinterpret_cast- หล่อสไตล์ C
(type)value - ฟังก์ชั่นหล่อสไตล์
type(value)
เราจะตัดสินใจได้อย่างไรว่าจะใช้ในกรณีใด
อะไรคือการใช้งานที่เหมาะสมของ:
static_castdynamic_castconst_castreinterpret_cast(type)valuetype(value)เราจะตัดสินใจได้อย่างไรว่าจะใช้ในกรณีใด
คำตอบ:
static_castเป็นนักแสดงคนแรกที่คุณควรพยายามใช้ มันทำสิ่งต่าง ๆ เช่นการแปลงโดยนัยระหว่างประเภท (เช่นintถึงfloatหรือตัวชี้ไปยังvoid*) และยังสามารถเรียกใช้ฟังก์ชันการแปลงที่ชัดเจน (หรือที่อยู่โดยนัย) ในหลายกรณีการระบุอย่างชัดเจนstatic_castไม่จำเป็น แต่เป็นสิ่งสำคัญที่จะต้องทราบว่าT(something)ไวยากรณ์นั้นเทียบเท่า(T)somethingและควรหลีกเลี่ยง (เพิ่มเติมในภายหลัง) อย่างไรก็ตามT(something, something_else)มีความปลอดภัยและรับประกันว่าจะเรียกตัวสร้าง
static_castยังสามารถส่งผ่านลำดับชั้นการสืบทอด ไม่จำเป็นเมื่อทำการร่ายขึ้นไป (ไปยังคลาสเบส) แต่เมื่อทำการร่ายลงมันสามารถใช้งานได้ตราบใดที่มันไม่ผ่านการvirtualถ่ายทอดทางพันธุกรรม มันไม่ได้ทำการตรวจสอบและมันเป็นพฤติกรรมที่ไม่ได้กำหนดเพื่อstatic_castลดลำดับชั้นเป็นประเภทที่ไม่ใช่ประเภทของวัตถุ
const_castสามารถใช้เพื่อลบหรือเพิ่มconstตัวแปร นักแสดง C ++ รายอื่นไม่สามารถลบได้ (ไม่ใช่แม้แต่reinterpret_cast) เป็นสิ่งสำคัญที่จะต้องทราบว่าการแก้ไขconstค่าเดิมนั้นไม่ได้กำหนดไว้หากตัวแปรดั้งเดิมคือconst; หากคุณใช้เพื่อconstถอดการอ้างอิงถึงสิ่งที่ไม่ได้ประกาศconstไว้มันปลอดภัย สิ่งนี้มีประโยชน์เมื่อโอเวอร์โหลดฟังก์ชันของสมาชิกตามconstเช่น นอกจากนี้ยังสามารถใช้เพื่อเพิ่มconstวัตถุเช่นการเรียกฟังก์ชั่นสมาชิกเกินพิกัด
const_castยังใช้งานได้เหมือนกันvolatileแม้ว่าจะเป็นเรื่องธรรมดาน้อยกว่า
dynamic_castใช้เฉพาะสำหรับการจัดการความแตกต่าง คุณสามารถใช้พอยน์เตอร์หรือการอ้างอิงถึงชนิด polymorphic ใด ๆ ไปยังประเภทคลาสอื่น ๆ (ประเภท polymorphic มีอย่างน้อยหนึ่งฟังก์ชั่นเสมือนจริงประกาศหรือสืบทอด) คุณสามารถใช้มันได้มากกว่าแค่การเหวี่ยงลง - คุณสามารถเหวี่ยงไปด้านข้างหรือแม้กระทั่งโซ่อื่น ๆ dynamic_castจะหาวัตถุที่ต้องการและส่งกลับมาถ้าเป็นไปได้ หากไม่สามารถทำได้จะส่งคืนnullptrในกรณีของตัวชี้หรือส่งstd::bad_castในกรณีของการอ้างอิง
dynamic_castมีข้อ จำกัด บางอย่าง มันจะไม่ทำงานหากมีวัตถุหลายประเภทที่เหมือนกันในลำดับชั้นการสืบทอด (ที่เรียกว่า 'เพชรที่น่ากลัว') และคุณไม่ได้ใช้การvirtualสืบทอด นอกจากนี้ยังสามารถผ่านการรับมรดกสาธารณะเท่านั้น - จะไม่สามารถเดินทางผ่านprotectedหรือprivateรับมรดกได้เสมอ อย่างไรก็ตามนี่เป็นปัญหาที่ไม่ค่อยเกิดขึ้นเนื่องจากรูปแบบของการสืบทอดนั้นหายาก
reinterpret_castเป็นนักแสดงที่อันตรายที่สุดและควรใช้อย่างไม่ จำกัด เปลี่ยนประเภทหนึ่งเป็นประเภทอื่นโดยตรง - เช่นการคัดเลือกค่าจากตัวชี้หนึ่งไปยังอีกประเภทหนึ่งหรือเก็บตัวชี้ไว้ในintหรือสิ่งแปลก ๆ อื่น ๆ ทุกประเภท ส่วนใหญ่การรับประกันเดียวที่คุณได้รับreinterpret_castคือโดยปกติถ้าคุณส่งผลลัพธ์กลับไปเป็นประเภทเดิมคุณจะได้รับค่าเดียวกันแน่นอน (แต่ไม่ใช่ถ้าประเภทกลางมีขนาดเล็กกว่าประเภทดั้งเดิม) มีจำนวนการแปลงที่reinterpret_castไม่สามารถทำได้เช่นกัน ส่วนใหญ่จะใช้สำหรับการแปลงแปลก ๆ และการปรับเปลี่ยนบิตเช่นเปลี่ยนกระแสข้อมูลดิบเป็นข้อมูลจริงหรือเก็บข้อมูลในบิตต่ำของตัวชี้ไปยังข้อมูลที่จัดตำแหน่ง
นักแสดงสไตล์ Cและนักแสดงสไตล์ฟังก์ชั่นมีการใช้งาน(type)objectหรือtype(object)ตามลำดับและเทียบเท่าฟังก์ชั่น พวกเขาถูกกำหนดให้เป็นคนแรกของต่อไปนี้ซึ่งประสบความสำเร็จ:
const_caststatic_cast (แม้ว่าจะเพิกเฉยต่อข้อ จำกัด การเข้าถึง)static_cast (ดูด้านบน) แล้ว const_castreinterpret_castreinterpret_castจากนั้น const_castดังนั้นจึงสามารถใช้แทนการปลดเปลื้องอื่น ๆ ได้ในบางกรณี แต่อาจเป็นอันตรายอย่างยิ่งเนื่องจากความสามารถในการเปลี่ยนเป็น a reinterpret_castและควรเลือกใช้หลังเมื่อต้องการการร่ายอย่างชัดเจนเว้นแต่คุณแน่ใจว่าstatic_castจะสำเร็จหรือreinterpret_castล้มเหลว . ถึงกระนั้นลองพิจารณาตัวเลือกที่ยาวและชัดเจนมากขึ้น
static_castcast -style casts ยังละเว้นการควบคุมการเข้าถึงเมื่อทำการ a ซึ่งหมายความว่าพวกมันมีความสามารถในการดำเนินการที่ไม่มี cast อื่น ๆ แม้ว่านี่จะเป็นกระบอง แต่ในใจของฉันก็เป็นอีกสาเหตุหนึ่งที่ทำให้หลีกเลี่ยงการแสดงใน C
constได้ (ไม่ถึงreinterpret_cast)" ... จริงเหรอ เกี่ยวกับreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))อะไร
reinterpret_castมักเป็นอาวุธที่เลือกใช้เมื่อจัดการกับชุดข้อมูลทึบแสงของ API
ใช้dynamic_castสำหรับการแปลงพอยน์เตอร์ / การอ้างอิงภายในลำดับชั้นการสืบทอด
ใช้static_castสำหรับการแปลงประเภทธรรมดา
ใช้reinterpret_castสำหรับการตีความรูปแบบบิตในระดับต่ำอีกครั้ง ใช้ด้วยความระมัดระวังอย่างยิ่ง
ใช้สำหรับการหล่อออกไปconst_cast const/volatileหลีกเลี่ยงสิ่งนี้หากคุณไม่ได้ใช้ API ที่ไม่ถูกต้อง
(มีการอธิบายทางทฤษฎีและแนวคิดมากมาย)
ด้านล่างนี้คือบางส่วนของตัวอย่างการปฏิบัติเมื่อใช้static_cast , dynamic_cast , const_cast , reinterpret_cast
(อ้างถึงสิ่งนี้เพื่อเข้าใจการอธิบาย: http://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast:
OnEventData(void* pData)
{
......
// pData is a void* pData,
// EventData is a structure e.g.
// typedef struct _EventData {
// std::string id;
// std:: string remote_id;
// } EventData;
// On Some Situation a void pointer *pData
// has been static_casted as
// EventData* pointer
EventData *evtdata = static_cast<EventData*>(pData);
.....
}
dynamic_cast:
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast:
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast:
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
static_cast<char*>(&val)หรือไม่
static_cast / จาก void *สำหรับทุกอย่างมีการปลดเปลื้องอื่น ๆ ประเภทreinterpret castใดก็ได้ที่char *ได้รับอนุญาตให้อ่านการเป็นตัวแทนของวัตถุใด ๆ - และหนึ่งในกรณีเดียวที่คำหลักนั้นมีประโยชน์ไม่ใช่เครื่องกำเนิดอาละวาดของพฤติกรรมการใช้งาน / ไม่ได้กำหนด แต่นี้จะไม่ถือว่าเป็น 'ปกติ' แปลงจึงไม่ได้รับอนุญาตตาม (ปกติ) static_castอนุรักษ์นิยมมาก
มันอาจช่วยได้ถ้าคุณรู้เรื่องภายในเล็กน้อย ...
static_cast
static_castสำหรับพวกเขาAการB, static_castโทรBคอนสตรัค 's ผ่านAเป็นพารามิเตอร์ หรือAอาจมีตัวดำเนินการแปลง (เช่นA::operator B()) หากBไม่มีคอนสตรัคเตอร์ดังกล่าวหรือAไม่มีโอเปอเรเตอร์การแปลงคุณจะได้รับข้อผิดพลาดในการรวบรวมเวลาA*การB*เสมอประสบความสำเร็จถ้า A และ B อยู่ในลำดับชั้นมรดก (หรือเป็นโมฆะ) มิฉะนั้นคุณจะได้รับการรวบรวมข้อผิดพลาดA&B&dynamic_cast
(Base*)การ(Derived*)อาจล้มเหลวหากตัวชี้ไม่จริงชนิดที่ได้มาA*ถึงB*หากการส่งไม่ถูกต้อง dynamic_cast จะส่งคืน nullptrA&การB&ถ้าหล่อไม่ถูกต้องแล้ว dynamic_cast จะโยนยกเว้น bad_castconst_cast
set<T>ซึ่งส่งคืนองค์ประกอบของมันเป็น const เท่านั้นเพื่อให้แน่ใจว่าคุณจะไม่เปลี่ยนคีย์ อย่างไรก็ตามหากคุณต้องการแก้ไขสมาชิกที่ไม่ได้ใช้คีย์ของวัตถุคุณควรจะยอมรับ คุณสามารถใช้ const_cast เพื่อลบความขุ่นT& SomeClass::foo() const T& SomeClass::foo() constเพื่อหลีกเลี่ยงการทำสำเนารหัสคุณสามารถใช้ const_cast เพื่อคืนค่าของฟังก์ชันหนึ่งจากอีกฟังก์ชันหนึ่งreinterpret_cast
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.คุณได้รับ UB ซึ่งอาจส่งผลให้ segfault ที่รันไทม์หากคุณโชคดี 2. Dynamic casts สามารถใช้ในการร่ายข้ามได้ 3. การปลดเปลื้อง Const อาจส่งผลให้ UB ในบางกรณี การใช้mutableอาจเป็นตัวเลือกที่ดีกว่าในการใช้ความมั่นคงทางตรรกะ
mutableการคัดเลือกข้าม ฯลฯ
ทำสิ่งนี้ตอบคำถามของคุณ?
ฉันไม่เคยใช้reinterpret_castและสงสัยว่าทำงานในกรณีที่ต้องการมันไม่ได้เป็นกลิ่นของการออกแบบที่ไม่ดี ในฐานรหัสฉันทำงานอยู่dynamic_castมีการใช้งานมาก ข้อแตกต่าง static_castคือการdynamic_castตรวจสอบรันไทม์ซึ่งอาจ (ปลอดภัยกว่า) หรืออาจจะไม่ (เพิ่มมากขึ้น) เป็นสิ่งที่คุณต้องการ (ดูmsdn )
reinterpret_castเพื่อแยกข้อมูลออกจากอาร์เรย์ ตัวอย่างเช่นถ้าฉันมีchar*บัฟเฟอร์ขนาดใหญ่ที่เต็มไปด้วยข้อมูลไบนารีที่อัดแน่นที่ฉันจำเป็นต้องย้ายผ่านและรับชนิดดั้งเดิมที่แตกต่างกันไป สิ่งนี้:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_castมันมีประโยชน์ไม่มากนัก
reinterpret_castใช้เพียงเหตุผลเดียว ฉันเคยเห็นข้อมูลวัตถุดิบที่เก็บไว้ในประเภทข้อมูล "หยด" ในฐานข้อมูลจากนั้นเมื่อข้อมูลถูกดึงจากฐานข้อมูลreinterpret_castจะใช้ในการเปลี่ยนข้อมูลดิบนี้เป็นวัตถุ
นอกเหนือจากคำตอบอื่น ๆ จนถึงตอนนี้นี่คือตัวอย่างที่static_castไม่ชัดเจนที่ไม่เพียงพอดังนั้นจึงreinterpret_castเป็นสิ่งจำเป็น สมมติว่ามีฟังก์ชั่นซึ่งในพารามิเตอร์ขาออกส่งกลับพอยน์เตอร์ไปยังวัตถุของคลาสที่แตกต่างกัน (ซึ่งไม่ได้แชร์คลาสพื้นฐานทั่วไป) ตัวอย่างจริงของฟังก์ชันดังกล่าวคือCoCreateInstance()(ดูพารามิเตอร์สุดท้ายซึ่งอันที่จริงแล้วvoid**) สมมติว่าคุณร้องขอคลาสของวัตถุเฉพาะจากฟังก์ชันนี้เพื่อให้คุณทราบล่วงหน้าถึงชนิดของตัวชี้ (ซึ่งคุณมักจะทำกับวัตถุ COM) ในกรณีนี้คุณไม่สามารถโยนตัวชี้ไปยังตัวชี้ของคุณเข้าvoid**กับstatic_cast: reinterpret_cast<void**>(&yourPointer)คุณจำเป็นต้อง
ในรหัส:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
อย่างไรก็ตามstatic_castทำงานได้กับพอยน์เตอร์ง่าย ๆ (ไม่ใช่พอยน์เตอร์ถึงพอยน์เตอร์) ดังนั้นโค้ดข้างต้นสามารถเขียนใหม่เพื่อหลีกเลี่ยงreinterpret_cast(ในราคาของตัวแปรพิเศษ) ด้วยวิธีต่อไปนี้:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
&static_cast<void*>(pNetFwPolicy2)แทนที่จะstatic_cast<void**>(&pNetFwPolicy2)?
ในขณะที่คำตอบอื่น ๆ ที่อธิบายไว้อย่างแตกต่างระหว่าง C ++ ปลดเปลื้องผมอยากจะเพิ่มบันทึกสั้น ๆ ว่าทำไมคุณไม่ควรใช้บรรยากาศแบบ C และ(Type) varType(var)
สำหรับผู้เริ่มต้น C + + C-style casts ดูเหมือนว่าเป็นการดำเนินการ superset บน C ++ casts (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) และบางคนอาจชอบพวกมันมากกว่า C ++ casts . อันที่จริงแล้วการหล่อแบบ C นั้นเป็นแบบซูเปอร์เซ็ตและสั้นกว่าในการเขียน
ปัญหาหลักของ C-style casts คือพวกเขาซ่อนเจตนาที่แท้จริงของนักพัฒนา cast-style cast สามารถทำการร่ายได้ทุกประเภทจากการร่ายความปลอดภัยตามปกติโดย static_cast <> () และ dynamic_cast <> () ถึงการร่ายที่อาจเป็นอันตรายเช่น const_cast <> () ซึ่งตัวดัดแปลง const สามารถลบได้ดังนั้นตัวแปร const สามารถแก้ไขและ reinterpret_cast <> () ที่สามารถตีความค่าจำนวนเต็มเป็นตัวชี้ได้อีกครั้ง
นี่คือตัวอย่าง
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
เหตุผลหลักที่เพิ่มการใช้งาน C ++ ในภาษานั้นเพื่อให้นักพัฒนาสามารถอธิบายความตั้งใจของเขาได้ - ทำไมเขาถึงต้องลงมือทำ ด้วยการใช้งาน C-style casts ที่ใช้ได้อย่างสมบูรณ์ใน C ++ คุณจะทำให้โค้ดของคุณอ่านน้อยลงและเกิดข้อผิดพลาดได้ง่ายขึ้นโดยเฉพาะสำหรับนักพัฒนาอื่น ๆ ที่ไม่ได้สร้างรหัสของคุณ ดังนั้นเพื่อให้โค้ดของคุณอ่านง่ายและชัดเจนคุณควรเลือก C + + casts มากกว่า C-style casts
นี่คือคำพูดสั้น ๆ จากหนังสือ Bjarne Stroustrup (ผู้เขียน C ++) ภาษาการเขียนโปรแกรม C ++ รุ่นที่ 4 - หน้า 302
คาสต์สไตล์ C นี้มีอันตรายมากกว่าตัวดำเนินการแปลงที่ตั้งชื่อไว้เนื่องจากสัญกรณ์นั้นยากที่จะสังเกตเห็นในโปรแกรมขนาดใหญ่และประเภทของการแปลงที่โปรแกรมเมอร์ตั้งใจไม่ชัดเจน
เพื่อความเข้าใจลองพิจารณาตัวอย่างโค้ดด้านล่าง:
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
รวบรวมเฉพาะบรรทัด (4) โดยไม่มีข้อผิดพลาด reinterpret_castเท่านั้นที่สามารถใช้ในการแปลงตัวชี้ไปยังวัตถุเป็นตัวชี้ไปเป็นชนิดวัตถุใด ๆ ที่ไม่เกี่ยวข้อง
สิ่งนี้ที่ควรสังเกตคือ: dynamic_castจะล้มเหลวในเวลาทำงานอย่างไรก็ตามในคอมไพเลอร์ส่วนใหญ่มันก็จะล้มเหลวในการคอมไพล์เพราะไม่มีหน้าที่เสมือนในโครงสร้างของตัวชี้ที่กำลังส่งซึ่งหมายถึงdynamic_castจะทำงานกับพอยน์เตอร์พหุนาม .
เมื่อใดที่จะใช้ C ++ cast :
static_castvs dynamic_castvs reinterpret_castinternals ดูบน downcast / upcast
ในคำตอบนี้ฉันต้องการเปรียบเทียบกลไกทั้งสามนี้ในตัวอย่างที่เป็นรูปธรรม upcast / downcast และวิเคราะห์สิ่งที่เกิดขึ้นกับพอยน์เตอร์ / หน่วยความจำ / แอสเซมบลีพื้นฐานเพื่อให้เข้าใจอย่างเป็นรูปธรรมเกี่ยวกับวิธีเปรียบเทียบ
ฉันเชื่อว่าสิ่งนี้จะให้สัญชาตญาณที่ดีในการปลดเปลื้องเหล่านั้นแตกต่างกัน
static_cast: ทำการชดเชยหนึ่งแอดเดรสที่รันไทม์ (ผลกระทบจากรันไทม์ต่ำ) และไม่มีการตรวจสอบความปลอดภัยว่าดาวน์ไลท์นั้นถูกต้องหรือไม่
dyanamic_cast: ที่อยู่เดียวกันตรงข้ามกับรันไทม์เช่นstatic_castกัน แต่ยังรวมถึงการตรวจสอบด้านความปลอดภัยที่มีราคาแพงว่า downcast นั้นถูกต้องโดยใช้ RTTI
การตรวจสอบความปลอดภัยนี้ช่วยให้คุณสามารถสอบถามได้ว่าตัวชี้คลาสพื้นฐานเป็นประเภทที่กำหนดเมื่อใช้งานจริงหรือไม่โดยการตรวจสอบการส่งคืนnullptrซึ่งบ่งชี้ว่าการดาวน์สตรีมที่ไม่ถูกต้อง
ดังนั้นหากรหัสของคุณไม่สามารถตรวจสอบได้nullptrและดำเนินการไม่ยกเลิกที่ถูกต้องคุณควรใช้static_castแทนการส่งแบบไดนามิก
หากการยกเลิกเป็นการกระทำเดียวที่โค้ดของคุณสามารถทำได้คุณอาจต้องการเปิดใช้งานการdynamic_castดีบักบิวด์ ( -NDEBUG) และใช้static_castอย่างอื่นเช่นที่ทำที่นี่เพื่อไม่ให้การรันเร็วของคุณช้าลง
reinterpret_cast: ไม่ทำอะไรเลยขณะรันไทม์หรือแม้แต่ออฟเซ็ตที่อยู่ ตัวชี้ต้องชี้ไปที่ประเภทที่ถูกต้องไม่แม้แต่คลาสพื้นฐานทำงาน โดยทั่วไปคุณไม่ต้องการสิ่งนี้เว้นแต่ว่ามีการสตรีมข้อมูลดิบไบต์
พิจารณาตัวอย่างรหัสต่อไปนี้:
main.cpp
#include <iostream>
struct B1 {
B1(int int_in_b1) : int_in_b1(int_in_b1) {}
virtual ~B1() {}
void f0() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
int int_in_b2;
};
struct D : public B1, public B2 {
D(int int_in_b1, int int_in_b2, int int_in_d)
: B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
void d() {}
int f2() { return 3; }
int int_in_d;
};
int main() {
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1, 2, 3};
// The memory layout must support the virtual method call use case.
b2s[0] = &b2;
// An upcast is an implicit static_cast<>().
b2s[1] = &d;
std::cout << "&d " << &d << std::endl;
std::cout << "b2s[0] " << b2s[0] << std::endl;
std::cout << "b2s[1] " << b2s[1] << std::endl;
std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;
// Now for some downcasts.
// Cannot be done implicitly
// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
// dp = (b2s[0]);
// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp = static_cast<D*>(b2s[0]);
std::cout << "static_cast<D*>(b2s[0]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = static_cast<D*>(b2s[1]);
std::cout << "static_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Segfault because dp is nullptr.
dp = dynamic_cast<D*>(b2s[0]);
std::cout << "dynamic_cast<D*>(b2s[0]) " << dp << std::endl;
//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = dynamic_cast<D*>(b2s[1]);
std::cout << "dynamic_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Undefined behaviour to an unrelated memory address because this
// did not calculate the offset to get from B2* to D*.
dp = reinterpret_cast<D*>(b2s[1]);
std::cout << "reinterpret_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}
รวบรวมเรียกใช้และถอดแยกชิ้นส่วนด้วย:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
ที่setarchจะใช้ในการปิดการใช้งาน ASLRเพื่อให้ง่ายต่อการเปรียบเทียบการทำงาน
เอาต์พุตที่เป็นไปได้:
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
ตอนนี้ดังที่กล่าวไว้ที่: https://en.wikipedia.org/wiki/Virtual_method_tableเพื่อสนับสนุนการเรียกใช้เมธอดเสมือนอย่างมีประสิทธิภาพโครงสร้างข้อมูลหน่วยความจำของDต้องมีลักษณะดังนี้:
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d
ความจริงที่สำคัญคือโครงสร้างข้อมูลของหน่วยความจำDที่มีอยู่ข้างในโครงสร้างหน่วยความจำที่เข้ากันได้กับที่ของB1และของB2ภายใน
ดังนั้นเราถึงข้อสรุปที่สำคัญ:
upcast หรือ downcast เพียงต้องการเปลี่ยนค่าตัวชี้โดยค่าที่รู้จักในเวลารวบรวม
ด้วยวิธีนี้เมื่อDถูกส่งผ่านไปยังอาเรย์ประเภทฐานนักแสดงประเภทจะคำนวณว่าออฟเซ็ตและชี้บางสิ่งที่ดูเหมือนว่าถูกต้องB2ในหน่วยความจำ:
b2s[1] = &d;
ยกเว้นว่าอันนี้มี vtable สำหรับDแทนB2และดังนั้นการโทรเสมือนทั้งหมดทำงานอย่างโปร่งใส
ตอนนี้ในที่สุดเราก็สามารถกลับไปพิมพ์แบบหล่อและวิเคราะห์ตัวอย่างที่เป็นรูปธรรมของเราได้
จากเอาต์พุต stdout เราเห็น:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
ดังนั้นการstatic_castกระทำโดยปริยายจึงทำการคำนวณออฟเซ็ตจากDโครงสร้างข้อมูลแบบเต็มอย่างถูกต้องที่ 0x7fffffffc930 ไปยังสิ่งที่B2คล้ายกันซึ่งอยู่ที่ 0x7fffffffcc940 นอกจากนี้เรายังอนุมานว่าสิ่งที่อยู่ระหว่าง 0x7fffffffc930 และ 0x7fffffffc940 นั้นน่าจะเป็นB1ข้อมูลและ vtable
จากนั้นในส่วน downcast ตอนนี้มันง่ายที่จะเข้าใจว่าสิ่งที่ไม่ถูกต้องล้มเหลวและทำไม:
static_cast<D*>(b2s[0]) 0x7fffffffc910: คอมไพเลอร์เพิ่งเพิ่มขึ้น 0x10 ที่ไบต์เวลารวบรวมเพื่อลองจากB2ไปยังที่มีD
แต่เนื่องจากb2s[0]ไม่ใช่ a Dตอนนี้จึงชี้ไปยังขอบเขตหน่วยความจำที่ไม่ได้กำหนด
ถอดชิ้นส่วนคือ:
49 dp = static_cast<D*>(b2s[0]);
0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax
0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433>
0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax
0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438>
0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
ดังนั้นเราจึงเห็นว่า GCC ทำ:
Dที่ไม่มีอยู่dynamic_cast<D*>(b2s[0]) 0: C ++ พบจริงว่าตัวละครนั้นไม่ถูกต้องและกลับมาแล้วnullptr!
ไม่มีวิธีนี้สามารถทำได้ในเวลารวบรวมและเราจะยืนยันว่าจากการถอดชิ้นส่วน:
59 dp = dynamic_cast<D*>(b2s[0]);
0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax
0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744>
0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx
0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D>
0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2>
0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi
0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt>
0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749>
0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
ครั้งแรกที่มีการตรวจสอบเป็นโมฆะและมันกลับเป็นโมฆะถ้า einput ที่เป็นโมฆะ
มิฉะนั้นจะตั้งค่าการขัดแย้งบางอย่างใน RDX, RSI และ RDI __dynamic_castและบริการโทร
ฉันไม่มีความอดทนที่จะวิเคราะห์สิ่งนี้เพิ่มเติมในขณะนี้ แต่อย่างที่คนอื่นพูดวิธีเดียวที่จะใช้งานได้คือ__dynamic_castการเข้าถึงโครงสร้างข้อมูลในหน่วยความจำ RTTI พิเศษที่แสดงถึงลำดับชั้นของชั้นเรียน
ดังนั้นจึงต้องเริ่มต้นจากB2รายการสำหรับตารางที่แล้วเดินลำดับชั้นนี้จนกว่าจะพบว่า vtable สำหรับDtypecast b2s[0]จาก
นี่คือเหตุผลที่นักแปลตีความซ้ำอาจมีราคาแพง! ที่นี่คือตัวอย่างที่แพทช์ซับหนึ่งแปลง a dynamic_castเป็น a static_castในโปรเจ็กต์ที่ซับซ้อนลดรันไทม์ 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940อันนี้แค่เชื่อว่าเราสุ่มสี่สุ่มห้า: เราบอกว่ามีDที่อยู่b2s[1]และคอมไพเลอร์ไม่มีการคำนวณออฟเซ็ต
แต่นี่เป็นสิ่งที่ผิดเพราะ D จริงๆแล้วที่ 0x7fffffffc930 สิ่งที่อยู่ที่ 0x7fffffffc940 คือโครงสร้าง B2 ที่เหมือนภายใน D! ดังนั้นการเข้าถึงถังขยะ
เราสามารถยืนยันสิ่งนี้จากความสยดสยอง -O0ชุมนุมที่ที่เพิ่งจะย้ายค่าไปรอบ ๆ :
70 dp = reinterpret_cast<D*>(b2s[1]);
0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax
0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)คำถามที่เกี่ยวข้อง:
ทดสอบกับ Ubuntu 18.04 amd64, GCC 7.4.0