2012-12-21 27 views
17

Chúng ta có thể xem biểu diễn của một đối tượng thuộc loại T bằng cách chuyển đổi một số T* trỏ vào đối tượng đó thành một char*. Ít nhất trong thực tế:Khi nào và chuyển đổi thành con trỏ char được phép như thế nào?

int x = 511; 
unsigned char* cp = (unsigned char*)&x; 
std::cout << std::hex << std::setfill('0'); 
for (int i = 0; i < sizeof(int); i++) { 
    std::cout << std::setw(2) << (int)cp[i] << ' '; 
} 

này kết quả đầu ra các đại diện của 511 trên hệ thống của tôi: ff 01 00 00.

Có (chắc chắn) một số hành vi được xác định thực hiện xảy ra ở đây. Mà phôi nào cho phép tôi chuyển đổi một số int* thành một số unsigned char* và chuyển đổi nào lấy hàm mũ? Tôi có đang gọi hành vi không xác định ngay sau khi tôi truyền không? Tôi có thể truyền bất kỳ loại T* nào như thế này không? Tôi có thể dựa vào điều gì khi làm điều này?

+3

Tôi không nghĩ rằng đó là hành vi không xác định, ít nhất là nếu bạn không sửa đổi dữ liệu.Nhưng kết quả sẽ phụ thuộc vào việc bạn có nền tảng là nhỏ hay lớn. – Synxis

+2

Lưu ý rằng điều này chỉ an toàn cho 'char *'. Việc tạo con trỏ để làm cho chúng đọc như các loại khác nhau gây ra các vấn đề với * aliasing *. Các ngôn ngữ C và C++ đảm bảo trình biên dịch trỏ đến các kiểu khác nhau không bao giờ có thể trỏ tới cùng một đối tượng để trình tối ưu hóa có thể thực hiện những việc như lưu trữ giá trị trong sổ đăng ký hoặc nạp một tải hoặc ghi ra khỏi vòng lặp. 'char *' là ngoại lệ duy nhất. Một 'char *' phải được giả định là bí danh với bất cứ điều gì, bởi vì serialization đến và từ đĩa và bộ đệm mạng. –

+2

@ZanLynx - Re "' char * 'là ngoại lệ duy nhất": Không hoàn toàn. Tiêu chuẩn này cũng cho phép chuyển đổi thành 'unsigned char *'. –

Trả lời

12

Giá phôi nào cho phép tôi chuyển đổi int* thành unsigned char*?

Dàn diễn viên kiểu C trong trường hợp này giống với reinterpret_cast<unsigned char*>.

Tôi có thể truyền bất kỳ loại T * nào như thế này không?

Có và không. Phần có: Bạn có thể an toàn truyền bất kỳ loại con trỏ nào đến char* hoặc unsigned char* (với các loại vòng loại thích hợp const và/hoặc volatile). Kết quả được xác định thực hiện, nhưng nó là hợp pháp.

Không phần nào: Tiêu chuẩn cho phép rõ ràng char*unsigned char* làm loại mục tiêu. Tuy nhiên, bạn không thể (ví dụ) an toàn đúc double* đến int*. Làm điều này và bạn đã vượt qua ranh giới từ hành vi được xác định thực hiện đến hành vi không xác định. Nó vi phạm quy tắc bí danh nghiêm ngặt.

+1

Aha, do đó, nó trông giống như (từ câu trả lời của @ GeneBushuyev và @ nobar) các diễn viên từ 'T *' cho bất kỳ 'U *' có kết quả không xác định (nhưng sẽ nếu tôi bỏ lại lần nữa) và nếu tôi chuyển sang bất cứ thứ gì trừ 'char *' hoặc 'unsigned char *' và sau đó * truy cập * đối tượng mặc dù con trỏ đó, tôi sẽ có hành vi không xác định (theo bí danh nghiêm ngặt). Câu trả lời hoàn hảo sẽ có cả hai điểm này. ;) –

2

Hành vi triển khai trong ví dụ của bạn là thuộc tính endianness của hệ thống của bạn, trong trường hợp này, CPU của bạn hơi kém. Giới thiệu về kiểu đúc, khi bạn tạo một int* đến char* tất cả những gì bạn đang làm là yêu cầu trình biên dịch diễn giải những gì cp trỏ đến dưới dạng char.

5

bản đồ dàn diễn viên của bạn để:

unsigned char* cp = reinterpret_cast<unsigned char*>(&x); 

Các đại diện cơ bản của một int là thực hiện được xác định, và xem nó như là nhân vật cho phép bạn kiểm tra đó. Trong trường hợp của bạn, nó là 32-bit chút endian.

Không có gì đặc biệt ở đây - phương pháp kiểm tra biểu diễn nội bộ này hợp lệ cho bất kỳ loại dữ liệu nào.

C++ 03 5.2.10.7: Một con trỏ đến một đối tượng có thể được chuyển đổi rõ ràng thành con trỏ đến đối tượng thuộc loại khác. Ngoại trừ việc chuyển đổi rvalue kiểu "pointer to T1" thành kiểu "pointer to T2" (trong đó T1 và T2 là các kiểu đối tượng và các yêu cầu liên kết của T2 không chặt chẽ hơn so với T1) và quay lại kiểu gốc của nó giá trị con trỏ ban đầu, kết quả của việc chuyển đổi con trỏ như vậy là không xác định.

Điều này cho thấy kết quả truyền trong hành vi không xác định. Nhưng thực tế, việc truyền từ bất kỳ kiểu con trỏ nào đến char* sẽ luôn cho phép bạn kiểm tra (và sửa đổi) biểu diễn bên trong của đối tượng được tham chiếu.

+0

Nói đúng ra, tiêu chuẩn không đảm bảo rằng 'char' nhỏ hơn' int'. – nobar

+2

Các tiêu chuẩn liên quan cho "quy tắc bí danh nghiêm ngặt" được cung cấp tại đây: http://stackoverflow.com/a/7005988/86967. Tóm tắt: Nếu bạn truy cập đối tượng qua 'char *' hoặc 'unsigned char *', không có vấn đề gì. – nobar

+0

Điều này có tính tiếp tuyến cao, nhưng thật thú vị khi lưu ý rằng quy tắc bí danh _strict_ cho thấy rằng việc sử dụng 'char *' có thể can thiệp vào tối ưu hóa. Đây là nơi mà từ khóa [hạn chế] (http://stackoverflow.com/questions/6434549/does-c11-add-the-c99-restrict-specifier-if-not-why-not) 'limits' có thể là hữu ích - mặc dù nó không áp dụng cho câu hỏi trong tầm tay, vì _aliasing_ chính xác là điểm của câu hỏi đã cho. – nobar

1

Việc truyền giữa các con trỏ luôn luôn có thể vì tất cả các con trỏ không có gì hơn địa chỉ bộ nhớ và bất kỳ loại nào, trong bộ nhớ, luôn có thể được xem như một chuỗi các byte.

Tuy nhiên, cách trình tự được hình thành phụ thuộc vào cách loại phân tách được biểu diễn trong bộ nhớ, và đó là ngoài phạm vi của các đặc tả C++. Điều đó nói rằng, trừ khi các trường hợp bệnh lý rất, bạn có thể mong rằng sự biểu diễn giống nhau trên tất cả mã được tạo ra bởi cùng một trình biên dịch cho tất cả các máy của cùng một nền tảng (hoặc gia đình), và bạn không nên mong đợi tương tự kết quả trên các nền tảng khác nhau. Nói chung một điều cần tránh là để thể hiện mối quan hệ giữa các kích cỡ loại như "được xác định trước": trong mẫu của bạn, bạn giả định sizeof(int) == 4*sizeof(char): đó không nhất thiết phải luôn đúng.

Nhưng nó luôn luôn là sự thật rằng sizeof (T) = N * sizeof (char), do đó bất cứ điều gì T luôn có thể được xem như là một số nguyên của char-s

+0

Tôi đang thiếu nơi OP giả định rằng 'sizeof (int) == 4 * sizeof (char)'. – phonetagger

+0

@phonetagger Emilio có thể đã trả lời phiên bản gốc của câu hỏi của tôi mà dựa vào đó là '4 * sizeof (char) '. –

+0

@sftrabbit - Tốt. Nhưng xóa bình luận của bạn nói rằng "Không cần phải xóa ..." – phonetagger

0

Trừ khi bạn có một nhà điều hành dàn diễn viên, sau đó một cast chỉ đơn giản là nói "xem" vùng nhớ đó theo một cách khác. Không có gì thực sự ưa thích, tôi sẽ nói.

Sau đó, bạn đang đọc vùng bộ nhớ byte-by-byte; miễn là bạn không thay đổi nó, nó chỉ là tốt. Tất nhiên, kết quả của những gì bạn nhìn thấy phụ thuộc rất nhiều từ nền tảng: suy nghĩ về tính cuối cùng, kích thước từ, đệm, v.v.

0

Chỉ cần đảo ngược thứ tự byte sau đó nó trở thành

00 00 01 ff 

Đó là 256 (01) + 255 (ff) = 511

Điều này là do platfom của bạn là little endian.

3

Dàn diễn viên kiểu C trong trường hợp này tương đương với reinterpret_cast. Tiêu chuẩn mô tả các ngữ nghĩa trong 5.2.10. Cụ thể, tại khoản 7:

"Một con trỏ đến một đối tượng có thể được chuyển đổi một cách rõ ràng để một con trỏ đến một type.70 đối tượng khác nhau Khi một prvalue v kiểu‘con trỏ đến T1’là chuyển đổi sang các loại “Con trỏ đến cvT2”, kết quả là static_cast<cvT2*>(static_cast<cvvoid*>(v)) nếu cả T1 và T2 là loại bố cục tiêu chuẩn (3.9) và yêu cầu căn chỉnh của T2 là không chặt chẽ hơn so với T1.Chuyển đổi giá trị loại “con trỏ đến T1 "Với loại" con trỏ đến T2 "(trong đó T1 và T2 là các kiểu đối tượng và trong đó yêu cầu căn chỉnh của T2 không chặt chẽ hơn so với các số của T1) và quay trở lại loại ban đầu của nó mang lại giá trị con trỏ ban đầu. Kết quả của bất kỳ chuyển đổi con trỏ khác như vậy là không xác định."

gì nó có nghĩa là trong trường hợp của bạn, các yêu cầu liên kết được thỏa mãn, và kết quả là không xác định.

+0

Ah, do đó, nó chỉ được xác định rõ khi bạn chuyển từ 'T *' sang 'U *' và quay lại 'T *'? Kết quả của một 'T *' cast vào một 'U *' chỉ là không xác định? Aha. –