C++標准轉換運算符reinterpret_cast
reinterpret_cast <new_type> (expression)
reinterpret_cast運算符是用來處理無關類型之間的轉換;它會產生一個新的值,這個值會有與原始參數(expressoin)有完全相同的比特位。
什么是無關類型?我沒有弄清楚,沒有找到好的文檔來說明類型之間到底都有些什么關系(除了類的繼承以外)。后半句倒是看出了reinterpret_cast的字面意思:重新解釋(類型的比特位)。我們真的可以隨意將一個類型值的比特位交給另一個類型作為它的值嗎?其實不然。
IBM的C++指南里倒是明確告訴了我們reinterpret_cast可以,或者說應該在什么地方用來作為轉換運算符:
- 從指針類型到一個足夠大的整數類型
- 從整數類型或者枚舉類型到指針類型
- 從一個指向函數的指針到另一個不同類型的指向函數的指針
- 從一個指向對象的指針到另一個不同類型的指向對象的指針
- 從一個指向類函數成員的指針到另一個指向不同類型的函數成員的指針
- 從一個指向類數據成員的指針到另一個指向不同類型的數據成員的指針
不過我在Xcode中測試了一下,事實上reinterpret_cast的使用並不局限在上邊所說的幾項的,任何類型的指針之間都可以互相轉換,都不會得到編譯錯誤。上述列出的幾項,可能 是Linux下reinterpret_cast使用的限制,也可能是IBM推薦我們使用reinterpret_cast的方式
所以總結來說:reinterpret_cast用在任意指針(或引用)類型之間的轉換;以及指針與足夠大的整數類型之間的轉換;從整數類型(包括枚舉類型)到指針類型,無視大小。
(所謂"足夠大的整數類型",取決於操作系統的參數,如果是32位的操作系統,就需要整形(int)以上的;如果是64位的操作系統,則至少需要長整形(long)。具體大小可以通過sizeof運算符來查看)。
reinterpret_cast有何作用
從上邊對reinterpret_cast介紹,可以感覺出reinterpret_cast是個很強大的運算符,因為它可以無視種族隔離,隨便搞。但就像生物的准則,不符合自然規律的隨意雜交只會得到不能長久生存的物種。隨意在不同類型之間使用reinterpret_cast,也之后造成程序的破壞和不能使用。
比如下邊的代碼typedef int (*FunctionPointer)(int);
int value = 21;
FunctionPointer funcP;
funcP = reinterpret_cast<FunctionPointer> (&value);
funcP(value);
我先用typedef定義了一個指向函數的指針類型,所指向的函數接受一個int類型作為參數。然后我用reinterpret_cast將一個整型的地址轉換成該函數類型並賦值給了相應的變量。最后,我還用該整形變量作為參數交給了指向函數的指針變量。
這個過程編譯器都成功的編譯通過,不過一旦運行我們就會得到"EXC_BAD_ACCESS"的運行錯誤,因為我們通過funcP所指的地址找到的並不是函數入口。
由此可知,reinterpret_cast雖然看似強大,作用卻沒有那么廣。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ網頁和MSDN的Visual C++也都指出:錯誤的使用reinterpret_cast很容易導致程序的不安全,只有將轉換后的類型值轉換回到其原始類型,這樣才是正確使用reinterpret_cast方式。
這樣說起來,reinterpret_cast轉換成其它類型的目的只是臨時的隱藏自己的什么(做個卧底?),要真想使用那個值,還是需要讓其露出真面目才行。那到底它在C++中有其何存在的價值呢?
MSDN的Visual C++ Developer Center 給出了它的使用價值:用來輔助哈希函數。下邊是MSNDN上的例子:
// expre_reinterpret_cast_Operator.cpp // compile with: /EHsc #include <iostream> // Returns a hash code based on an address unsigned short Hash( void *p ) { unsigned int val = reinterpret_cast<unsigned int>( p ); return ( unsigned short )( val ^ (val >> 16)); } using namespace std; int main() { int a[20]; for ( int i = 0; i < 20; i++ ) cout << Hash( a + i ) << endl; } //如果跟我一樣是64位的系統,可能需要將unsigned int改成 unsigned long才能運行。
這段代碼適合體現哈希的思想,暫時不做深究,但至少看Hash函數里面的操作,也能體會到,對整數的操作顯然要對地址操作更方便。在集合中存放整形數值,也要比存放地址更具有擴展性(當然如果存void *擴展性也是一樣很高的),唯一損失的可能就是存取的時候整形和地址的轉換(這完全可以忽略不計)。
不過可讀性可能就不高,所以在這種情況下使用的時候,就可以用typedef來定義個指針類型:typedef unsigned int PointerType;
這樣不是更棒,當我們在64位機器上運行的時候,只要改成:typedef unsigned long PointerType;
當reinterpret_cast面對const
IBM的C++指南指出:reinterpret_cast不能像const_cast那樣去除const修飾符。 這是什么意思呢?代碼還是最直觀的表述:
int main() { typedef void (*FunctionPointer)(int); int value = 21; const int* pointer = &value; //int * pointer_r = reinterpret_cast<int*> (pointer); // Error: reinterpret_cast from type 'const int*' to type 'int*' casts away constness FunctionPointer funcP = reinterpret_cast<FunctionPointer> (pointer); }
例子里,我們像前面const_cast一篇舉到的例子那樣,希望將指向const的指針用運算符轉換成非指向const的指針。但是當實用reinterpret_cast的時候,編譯器直接報錯組織了該過程。這就體現出了const_cast的獨特之處。
但是,例子中還有一個轉換是將指向const int的指針付給指向函數的指針,編譯順利通過編譯,當然結果也會跟前面的例子一樣是無意義的。
如果我們換一種角度來看,這似乎也是合理的。因為const int* p = &value;
int * const q = &value;
這兩個語句的含義是不同的,前者是"所指內容不可變",后者則是"指向的地址不可變"(具體參考此處)。因此指向函數的指針默認應該就帶有"所指內容不可變"的特性。
畢竟函數在編譯之后,其操作過程就固定在那里了,我們唯一能做的就是傳遞一些參數給指針,而無法改變已編譯函數的過程。所以從這個角度來想,上邊例子使用reinterpret_cast從const int * 到FunctionPointer轉換就變得合理了,因為它並沒有去除const限定
reinterpret_cast <new_type> (expression)
reinterpret_cast是強制類型轉換符!他是用來處理無關類型轉換的,通常為操作數的位模式提供較低層次的重新解釋!但是他僅僅是重新解釋了給出的對象的比特模型,並沒有進行二進制的轉換!
他是用在任意的指針之間的轉換,引用之間的轉換,指針和足夠大的int型之間的轉換,整數到指針的轉換,在家面的文章中將給出!
來看一個例子:
OK, 在這里你可以看到reinterpret_cast的強大作用,但是要注意的是,他並沒有進行二進制的轉換,pc指向的真實對象其實還是int的,不是char~
- int *pi;
- char *pc = reinterpret_cast<char*>(pi);
對於reinterpret_cast運算符要謹慎使用,下面給出一些使用的地方:
參考IBM C++
- A pointer to any integral type large enough to hold it (指針轉向足夠大的整數類型)
- A value of integral or enumeration type to a pointer (從整形或者enum枚舉類型轉換為指針)
- A pointer to a function to a pointer to a function of a different type (從指向函數的指針轉向另一個不同類型的指向函數的指針)
- A pointer to an object to a pointer to an object of a different type (從一個指向對象的指針轉向另一個不同類型的指向對象的指針)
- A pointer to a member to a pointer to a member of a different class or type, if the types of the members are both function types or object types (從一個指向成員的指針轉向另一個指向類成員的指針!或者是類型,如果類型的成員和函數都是函數類型或者對象類型)
這些是 IBM C++推薦給我們的使用方式!reinterpret_cast是為了映射到一個完全不同類型的意思,這個關鍵詞在我們需要把類型映射回原有類型時用到它。我們映射到的類型僅僅是為了故弄玄虛和其他目的,這是所有映射中最危險的。(這句話是C++編程思想中的原話)
大家使用的時候千萬記住,不要亂使用!錯誤使用很容易導致程序的不安全!
Misuse of the reinterpret_cast operator can easily be unsafe. 這是MSDN 的原話!
當然reinterpret_cast對 const, volatile, or __unaligned 也是沒有用處的!
MSDN 上面給了一個實際的用處:哈希函數輔助
- // expre_reinterpret_cast_Operator.cpp
- // compile with: /EHsc
- #include <iostream>
- // Returns a hash code based on an address
- unsigned short Hash( void *p ) {
- unsigned int val = reinterpret_cast<unsigned int>( p );
- return ( unsigned short )( val ^ (val >> 16));
- }
- using namespace std;
- int main() {
- int a[20];
- for ( int i = 0; i < 20; i++ )
- cout << Hash( a + i ) << endl;
- }
主要是涉及數據在內存中的表示。
看看下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <iostream>
using
namespace
std;
void
main()
{
int
i = 875770417;
//在內存中的表示從低位到高位依次是0x31 0x32 0x33 0x34
cout<<i<<
" "
;
char
* p =
reinterpret_cast
<
char
*>(&i);
for
(
int
j=0; j<4; j++)
cout<<p[j];
cout<<endl;
float
f = 0.00000016688933;
//在內存中的表示從低位到高位依次也是0x31 0x32 0x33 0x34
cout <<f<<
" "
;
p =
reinterpret_cast
<
char
*>(&f);
for
(j=0; j<4; j++)
cout<<p[j];
cout<<endl;
}
|
不管是int型的i還是float型的f,經過reinterpret_cast<char*>(&addr)的轉換后,輸出都是"1234"
數據的表示相同,就看編譯器怎么來解釋數據,也就是語義表達的問題。
reinterpret_cast的轉換就是不理會數據本來的語義,而重新賦予它char*的語義。
reinterpret_cast<char*>(&st) 這個怎么理解
reinterpret_cast是C++新標准下的強制類型轉換,這里將&st的指針轉換為char*型