Các cách sử dụng thích hợp của:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Diễn viên kiểu C
(type)value
- Diễn viên theo phong cách chức năng
type(value)
Làm thế nào để một người quyết định sử dụng trong trường hợp cụ thể?
Các cách sử dụng thích hợp của:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Làm thế nào để một người quyết định sử dụng trong trường hợp cụ thể?
Câu trả lời:
static_cast
là diễn viên đầu tiên bạn nên cố gắng sử dụng. Nó thực hiện những việc như chuyển đổi ngầm giữa các loại (ví dụ như int
để float
, hoặc con trỏ tới void*
), và nó cũng có thể gọi chức năng chuyển đổi rõ ràng (hoặc những tiềm ẩn). Trong nhiều trường hợp, việc nêu rõ ràng static_cast
là không cần thiết, nhưng điều quan trọng cần lưu ý là T(something)
cú pháp tương đương (T)something
và nên tránh (nhiều hơn về điều đó sau). A T(something, something_else)
là an toàn, tuy nhiên, và được đảm bảo để gọi hàm tạo.
static_cast
cũng có thể truyền qua hệ thống phân cấp thừa kế. Không cần thiết khi truyền lên (đối với lớp cơ sở), nhưng khi truyền xuống, nó có thể được sử dụng miễn là nó không truyền qua virtual
kế thừa. Tuy nhiên, nó không thực hiện kiểm tra và đó là hành vi không xác định để static_cast
phân cấp thành một loại không thực sự là loại đối tượng.
const_cast
có thể được sử dụng để loại bỏ hoặc thêm const
vào một biến; không có diễn viên C ++ nào khác có khả năng loại bỏ nó (thậm chí không reinterpret_cast
). Điều quan trọng cần lưu ý là việc sửa đổi một const
giá trị trước đây chỉ không được xác định nếu biến ban đầu là const
; nếu bạn sử dụng nó để loại const
bỏ một tham chiếu đến một cái gì đó không được khai báo const
, nó an toàn. Điều này có thể hữu ích khi quá tải các chức năng thành viên dựa trên const
, ví dụ. Nó cũng có thể được sử dụng để thêm const
vào một đối tượng, chẳng hạn như để gọi quá tải hàm thành viên.
const_cast
cũng hoạt động tương tự volatile
, mặc dù điều đó ít phổ biến hơn.
dynamic_cast
được sử dụng riêng để xử lý đa hình. Bạn có thể truyền một con trỏ hoặc tham chiếu đến bất kỳ loại đa hình nào cho bất kỳ loại lớp nào khác (một loại đa hình có ít nhất một hàm ảo, được khai báo hoặc kế thừa). Bạn có thể sử dụng nó cho nhiều thứ hơn là chỉ truyền xuống - bạn có thể bỏ sang một bên hoặc thậm chí lên một chuỗi khác. Các dynamic_cast
sẽ tìm ra các đối tượng mong muốn và gửi lại nếu có thể. Nếu không thể, nó sẽ trả về nullptr
trong trường hợp con trỏ hoặc ném std::bad_cast
trong trường hợp tham chiếu.
dynamic_cast
có một số hạn chế, mặc dù. Nó không hoạt động nếu có nhiều đối tượng cùng loại trong hệ thống phân cấp thừa kế (cái gọi là 'viên kim cương đáng sợ') và bạn không sử dụng tính virtual
kế thừa. Nó cũng chỉ có thể đi qua thừa kế công cộng - nó sẽ luôn luôn không đi qua protected
hoặc private
thừa kế. Điều này hiếm khi là một vấn đề, tuy nhiên, vì các hình thức thừa kế như vậy là rất hiếm.
reinterpret_cast
là diễn viên nguy hiểm nhất, và nên được sử dụng rất ít. Nó biến một loại trực tiếp thành loại khác - chẳng hạn như truyền giá trị từ con trỏ này sang con trỏ khác hoặc lưu trữ một con trỏ trong một int
hoặc tất cả các loại điều khó chịu khác. Phần lớn, đảm bảo duy nhất bạn nhận được reinterpret_cast
là thông thường nếu bạn đưa kết quả trở lại loại ban đầu, bạn sẽ nhận được cùng một giá trị (nhưng không phải nếu loại trung gian nhỏ hơn loại ban đầu). Có một số chuyển đổi reinterpret_cast
không thể làm quá. Nó được sử dụng chủ yếu cho các chuyển đổi và thao tác bit đặc biệt kỳ lạ, như biến luồng dữ liệu thô thành dữ liệu thực tế hoặc lưu trữ dữ liệu trong các bit thấp của con trỏ thành dữ liệu được căn chỉnh.
Diễn viên kiểu C và diễn viên kiểu chức năng là các diễn viên sử dụng (type)object
hoặc type(object)
, tương ứng, và tương đương về chức năng. Chúng được định nghĩa là lần đầu tiên sau đây thành công:
const_cast
static_cast
(mặc dù bỏ qua các hạn chế truy cập)static_cast
(xem ở trên), sau đó const_cast
reinterpret_cast
reinterpret_cast
, sau đó const_cast
Do đó, nó có thể được sử dụng để thay thế cho các diễn viên khác trong một số trường hợp, nhưng có thể cực kỳ nguy hiểm vì khả năng chuyển thành a reinterpret_cast
và nên được ưu tiên khi sử dụng phép đúc rõ ràng, trừ khi bạn chắc chắn static_cast
sẽ thành công hoặc reinterpret_cast
sẽ thất bại . Ngay cả sau đó, xem xét các tùy chọn dài hơn, rõ ràng hơn.
Các diễn viên kiểu C cũng bỏ qua kiểm soát truy cập khi thực hiện a static_cast
, điều đó có nghĩa là họ có khả năng thực hiện một thao tác mà không có diễn viên nào khác có thể làm được. Tuy nhiên, điều này chủ yếu là một loại bùn, và trong suy nghĩ của tôi chỉ là một lý do khác để tránh các diễn viên kiểu C.
const
(thậm chí không reinterpret_cast
)" ... thực sự? Thế còn reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
thường là vũ khí được lựa chọn khi xử lý tập hợp các loại dữ liệu mờ của API
Sử dụng dynamic_cast
để chuyển đổi con trỏ / tham chiếu trong hệ thống phân cấp thừa kế.
Sử dụng static_cast
cho chuyển đổi loại thông thường.
Sử dụng reinterpret_cast
để diễn giải lại mức độ thấp của các mẫu bit. Sử dụng hết sức thận trọng.
Sử dụng const_cast
để đúc đi const/volatile
. Tránh điều này trừ khi bạn bị mắc kẹt khi sử dụng API const-không chính xác.
(Rất nhiều lời giải thích lý thuyết và khái niệm đã được đưa ra ở trên)
Dưới đây là một số ví dụ thực tế khi tôi sử dụng static_cast , Dynamic_cast , const_cast , reinterpret_cast .
(Cũng tham khảo điều này để hiểu giải thích: 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);
.....
}
động_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
chỉ hoạt động giữa các loại với các chuyển đổi được xác định, quan hệ rõ ràng theo kế thừa hoặc đến / từ void *
. Đối với tất cả mọi thứ khác, có phôi khác. reinterpret cast
đối với bất kỳ char *
loại nào cũng được phép cho phép đọc đại diện của bất kỳ đối tượng nào - và một trong những trường hợp duy nhất mà từ khóa đó hữu ích, không phải là một trình tạo tràn lan của hành vi triển khai- / không xác định. Nhưng điều này không được coi là chuyển đổi 'bình thường', do đó không được phép (thường) rất bảo thủ static_cast
.
Nó có thể hữu ích nếu bạn biết một chút nội bộ ...
tĩnh_cast
static_cast
cho họ.A
để B
, static_cast
cuộc gọi B
constructor 's đi qua A
như param. Ngoài ra, A
có thể có một toán tử chuyển đổi (tức là A::operator B()
). Nếu B
không có hàm tạo như vậy hoặc A
không có toán tử chuyển đổi, thì bạn sẽ gặp lỗi thời gian biên dịch.A*
để B*
luôn thành công nếu A và B nằm trong hệ thống phân cấp thừa kế (hoặc void) nếu không bạn sẽ gặp lỗi biên dịch.A&
đến B&
.động_cast
(Base*)
để (Derived*)
có thể thất bại nếu con trỏ không phải là thực sự là loại có nguồn gốc.A*
với B*
, nếu cast không hợp lệ thì Dynamic_cast sẽ trả về nullptr.A&
với B&
nếu cast không hợp lệ thì Dynamic_cast sẽ ném ngoại lệ bad_cast.const_cast
set<T>
chỉ trả về các phần tử của nó dưới dạng const để đảm bảo bạn không thay đổi khóa của nó. Tuy nhiên, nếu mục đích của bạn là sửa đổi các thành viên không quan trọng của đối tượng thì nó sẽ ổn thôi. Bạn có thể sử dụng const_cast để loại bỏ constness.T& SomeClass::foo()
cũng như const T& SomeClass::foo() const
. Để tránh trùng lặp mã, bạn có thể áp dụng const_cast để trả về giá trị của hàm này từ hàm khác.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.
Bạn nhận được UB có thể dẫn đến một segfault khi chạy nếu bạn may mắn. 2. phôi động cũng có thể được sử dụng trong đúc chéo. 3. Các phôi phôi có thể dẫn đến UB trong một số trường hợp. Sử dụng mutable
có thể là một lựa chọn tốt hơn để thực hiện hằng số logic.
mutable
, đúc chéo, v.v.
Điều này có trả lời câu hỏi của bạn không?
Tôi chưa bao giờ sử dụng reinterpret_cast
, và tự hỏi liệu chạy vào một trường hợp cần nó không phải là một mùi của thiết kế xấu. Trong cơ sở mã tôi làm việc dynamic_cast
được sử dụng rất nhiều. Sự khác biệt với static_cast
là việc dynamic_cast
kiểm tra thời gian chạy có thể (an toàn hơn) hoặc có thể không (chi phí cao hơn) là những gì bạn muốn (xem msdn ).
reinterpret_cast
để trích xuất các mẩu dữ liệu ra khỏi một mảng. Ví dụ, nếu tôi có một char*
bộ đệm lớn chứa đầy dữ liệu nhị phân được đóng gói mà tôi cần di chuyển qua và nhận các nguyên hàm riêng lẻ thuộc các loại khác nhau. Một cái gì đó như thế này: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
, không có nhiều sử dụng cho nó.
reinterpret_cast
được sử dụng cho một lý do. Tôi đã thấy dữ liệu đối tượng thô được lưu trữ vào kiểu dữ liệu "blob" trong cơ sở dữ liệu, sau đó khi dữ liệu được truy xuất từ cơ sở dữ liệu, reinterpret_cast
được sử dụng để biến dữ liệu thô này thành đối tượng.
Ngoài các câu trả lời khác cho đến nay, đây là ví dụ không rõ ràng khi static_cast
không đủ để điều đó reinterpret_cast
là cần thiết. Giả sử có một hàm trong một tham số đầu ra trả về các con trỏ tới các đối tượng của các lớp khác nhau (không chia sẻ một lớp cơ sở chung). Một ví dụ thực tế của chức năng này là CoCreateInstance()
(xem tham số cuối cùng, trên thực tế void**
). Giả sử bạn yêu cầu lớp đối tượng cụ thể từ hàm này, để bạn biết trước loại cho con trỏ (mà bạn thường làm cho các đối tượng COM). Trong trường hợp này, bạn không thể bỏ con trỏ tới con trỏ của mình void**
bằng static_cast
: bạn cần reinterpret_cast<void**>(&yourPointer)
.
Trong mã:
#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) );
Tuy nhiên, static_cast
hoạt động cho các con trỏ đơn giản (không phải con trỏ tới con trỏ), do đó, đoạn mã trên có thể được viết lại để tránh reinterpret_cast
(với giá của một biến phụ) theo cách sau:
#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)
thay vì static_cast<void**>(&pNetFwPolicy2)
?
Trong khi các câu trả lời khác mô tả độc đáo tất cả sự khác biệt giữa các phôi C ++, tôi muốn thêm một ghi chú ngắn tại sao bạn không nên sử dụng phôi kiểu C (Type) var
vàType(var)
.
Đối với người mới bắt đầu C ++, các kiểu phôi C trông giống như hoạt động của superset so với các phôi C ++ (static_cast <> (), Dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) và ai đó có thể thích chúng hơn các phôi C ++ . Trong thực tế, diễn viên kiểu C là superset và ngắn hơn để viết.
Vấn đề chính của dàn diễn viên theo phong cách C là họ che giấu ý định thực sự của nhà phát triển. Các phôi kiểu C có thể thực hiện hầu như tất cả các kiểu truyền từ các phôi thông thường an toàn được thực hiện bởi static_cast <> () và Dynamic_cast <> () đến các phôi có khả năng nguy hiểm như const_cast <> (), trong đó công cụ sửa đổi const có thể được loại bỏ để các biến const có thể được sửa đổi và reinterpret_cast <> () thậm chí có thể diễn giải lại các giá trị nguyên cho con trỏ.
Đây là mẫu.
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.
Lý do chính tại sao các diễn viên C ++ được thêm vào ngôn ngữ là để cho phép một nhà phát triển làm rõ ý định của anh ta - tại sao anh ta sẽ thực hiện vai diễn đó. Bằng cách sử dụng các kiểu phôi C hoàn toàn hợp lệ trong C ++, bạn đang làm cho mã của mình ít đọc hơn và dễ bị lỗi hơn, đặc biệt là đối với các nhà phát triển khác không tạo mã của bạn. Vì vậy, để làm cho mã của bạn dễ đọc và rõ ràng hơn, bạn nên luôn thích các phôi C ++ hơn các phôi kiểu C.
Dưới đây là một trích dẫn ngắn từ cuốn sách của Bjarne Stroustrup (tác giả của C ++) Cuốn sách Ngôn ngữ lập trình C ++ phiên bản thứ 4 - trang 302.
Truyền kiểu C này nguy hiểm hơn nhiều so với các toán tử chuyển đổi được đặt tên vì ký hiệu khó phát hiện hơn trong một chương trình lớn và loại chuyển đổi mà lập trình viên dự định không rõ ràng.
Để hiểu, hãy xem xét đoạn mã dưới đây:
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;
}
Chỉ dòng (4) biên dịch không có lỗi. Chỉ reinterpret_cast có thể được sử dụng để chuyển đổi một con trỏ thành một đối tượng thành một con trỏ thành một loại đối tượng không liên quan.
Một điều cần lưu ý là: Dynamic_cast sẽ thất bại trong thời gian chạy, tuy nhiên trên hầu hết các trình biên dịch, nó cũng sẽ không biên dịch được vì không có các hàm ảo trong cấu trúc của con trỏ được truyền, có nghĩa là Dynamic_cast sẽ chỉ hoạt động với các con trỏ lớp đa hình .
Khi nào nên sử dụng C ++ cast :
static_cast
đấu dynamic_cast
vớireinterpret_cast
nội bộ xem trên một downcast / upcast
Trong câu trả lời này, tôi muốn so sánh ba cơ chế này trên một ví dụ upcast / downcast cụ thể và phân tích những gì xảy ra với các con trỏ / bộ nhớ / lắp ráp cơ bản để đưa ra một sự hiểu biết cụ thể về cách chúng so sánh.
Tôi tin rằng điều này sẽ mang đến một trực giác tốt về cách các diễn viên đó khác nhau:
static_cast
: có một địa chỉ bù vào thời gian chạy (tác động thời gian chạy thấp) và không có kiểm tra an toàn nào cho thấy một downcast là chính xác.
dyanamic_cast
: thực hiện cùng một địa chỉ bù vào thời gian chạy như thế nào static_cast
, nhưng cũng như một kiểm tra an toàn đắt tiền rằng một downcast là chính xác bằng RTTI.
Kiểm tra an toàn này cho phép bạn truy vấn nếu một con trỏ lớp cơ sở thuộc loại đã cho trong thời gian chạy bằng cách kiểm tra trả về trong nullptr
đó chỉ ra một downcast không hợp lệ.
Do đó, nếu mã của bạn không thể kiểm tra điều đó nullptr
và thực hiện hành động không hủy bỏ hợp lệ, bạn chỉ nên sử dụng static_cast
thay vì truyền động.
Nếu hủy bỏ là hành động duy nhất mà mã của bạn có thể thực hiện, có thể bạn chỉ muốn kích hoạt các dynamic_cast
bản dựng gỡ lỗi ( -NDEBUG
) và sử dụng static_cast
khác, ví dụ như được thực hiện ở đây , để không làm chậm quá trình chạy nhanh của bạn.
reinterpret_cast
: không làm gì trong thời gian chạy, thậm chí không bù địa chỉ. Con trỏ phải trỏ chính xác đến đúng loại, thậm chí không có lớp cơ sở hoạt động. Bạn thường không muốn điều này trừ khi các luồng byte thô có liên quan.
Xem xét ví dụ mã sau:
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;
}
Biên dịch, chạy và tháo rời với:
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
nơi setarch
được sử dụng để vô hiệu hóa ASLR để giúp so sánh các lần chạy dễ dàng hơn.
Sản lượng có thể:
&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
Bây giờ, như đã đề cập tại: https://en.wikipedia.org/wiki/Virtual_method_table để hỗ trợ các cuộc gọi phương thức ảo một cách hiệu quả, cấu trúc dữ liệu bộ nhớ D
phải trông giống như:
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
Thực tế quan trọng là cấu trúc dữ liệu bộ nhớ D
chứa bên trong cấu trúc bộ nhớ tương thích với cấu trúc bộ nhớ trong B1
và B2
bên trong.
Vì vậy, chúng tôi đi đến kết luận quan trọng:
một upcast hoặc downcast chỉ cần thay đổi giá trị con trỏ theo một giá trị được biết tại thời điểm biên dịch
Theo cách này, khi D
được chuyển đến mảng loại cơ sở, kiểu cast thực sự tính toán phần bù đó và chỉ ra thứ gì đó trông giống hệt như một giá trị B2
trong bộ nhớ:
b2s[1] = &d;
ngoại trừ việc cái này có vtable D
thay cho B2
, và do đó tất cả các cuộc gọi ảo hoạt động trong suốt.
Bây giờ, cuối cùng chúng ta có thể quay lại kiểu đúc và phân tích ví dụ cụ thể của chúng ta.
Từ đầu ra tiêu chuẩn, chúng ta thấy:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Do đó, ẩn static_cast
được thực hiện ở đó đã tính toán chính xác phần bù từ D
cấu trúc dữ liệu đầy đủ tại 0x7fffffffc930 đến mức B2
tương tự ở mức 0x7fffffffc940. Chúng tôi cũng suy luận rằng những gì nằm giữa 0x7fffffffc930 và 0x7fffffffc940 có thể là B1
dữ liệu và vtable.
Sau đó, trên các phần downcast, giờ đây thật dễ hiểu làm thế nào những phần không hợp lệ thất bại và tại sao:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: trình biên dịch chỉ tăng 0x10 tại các byte thời gian biên dịch để thử và chuyển từ a B2
sang chứaD
Nhưng vì b2s[0]
không phải là mộtD
nên giờ nó chỉ đến một vùng nhớ không xác định.
Việc tháo gỡ là:
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)
vì vậy chúng tôi thấy rằng GCC không:
D
cái không tồn tạidynamic_cast<D*>(b2s[0]) 0
: C ++ thực sự thấy rằng dàn diễn viên không hợp lệ và được trả lại nullptr
!
Không có cách nào có thể được thực hiện tại thời điểm biên dịch và chúng tôi sẽ xác nhận điều đó từ quá trình tháo gỡ:
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)
Đầu tiên, có một kiểm tra NULL và nó trả về NULL nếu thông số đó là NULL.
Mặt khác, nó thiết lập một số đối số trong RDX, RSI và RDI và các cuộc gọi __dynamic_cast
.
Bây giờ tôi không đủ kiên nhẫn để phân tích vấn đề này, nhưng như những người khác đã nói, cách duy nhất để làm việc này là __dynamic_cast
truy cập vào một số cấu trúc dữ liệu trong bộ nhớ RTTI đại diện cho hệ thống phân cấp lớp.
Do đó, nó phải bắt đầu từ B2
mục nhập cho bảng đó, sau đó đi theo cấu trúc phân cấp lớp này cho đến khi nhận thấy rằng vtable cho một D
kiểu chữ từb2s[0]
.
Đây là lý do tại sao diễn viên reinterpret có khả năng đắt tiền! Dưới đây là một ví dụ trong đó một bản vá lót chuyển đổi dynamic_cast
thành một static_cast
trong một dự án phức tạp đã giảm 33% thời gian chạy! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
Điều này chỉ tin tưởng chúng tôi một cách mù quáng: chúng tôi đã nói rằng có một D
địa chỉ b2s[1]
và trình biên dịch không tính toán bù.
Nhưng điều này là sai, bởi vì D thực sự ở 0x7fffffffc930, ở 0x7fffffffc940 là cấu trúc giống như B2 bên trong D! Vì vậy, rác được truy cập.
Chúng tôi có thể xác nhận điều này từ -O0
hội đồng khủng khiếp chỉ di chuyển giá trị xung quanh:
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)
Câu hỏi liên quan:
Đã thử nghiệm trên Ubuntu 18.04 amd64, GCC 7.4.0.