void型のポインタとint型を相互変換するなという話
int型をvoid *に変換する場合も、その逆の場合も、32bitだと問題なく動くことが多いため、
コンパイラもエラーにしない場合が多いです。
ですが、64bitだと問題が起きることが多いため、64bitを対象にした場合にエラー扱いをする場合があり、
突然わいて出る大量のエラーに悩まされる事があります…(´・_・`)
intからvoid型のポインタへの変換
int型の値をvoid *を利用して保持したい場合、前の例のように、int型を保持するオブジェクトを作り、
その中に値を入れた上で、そのオブジェクトへのポインタを持たせる必要があります。
struct Container{
void* data;
};
struct Num{
int n;
};
// int型を保存する
Num* numPointer = new Num();
numPointer->n = 42;
Container con;
con.data = (void *)numPointer;
// 42を取り出す
int number = ((Num*)con.data)->n;
// newしたので必ず破棄する
delete con.data;
con.data = NULL;
ですが、世の中にはたまにvoid *にint型(やその他のプリミティブ型)を代入する不届き者がいます。
int num = 42;
Container con;
con.data = (void*)num;
// 42を取り出す
int number = (int)con.data;
void はポインタのため、32bit環境ではvoidは32bitであり、intも基本的には32bitで同じサイズのため、
32bit環境に限れば問題なくコンパイル、実行が出来ます。
(エラーになる場合は、g++ test.cpp -m32で32bitのみのコンパイルが出来ます)
ですが、当然ながら64bit環境ではvoid *は64bitのため、intが32bitの場合は足りない分が消滅します。
多くのコンパイラでは64bitでコンパイルしようとしたときに、このキャストが行われるとエラーを出してくれますが、
32bitのみコンパイルした場合は出してくれない場合があるため、
対応しようとした時に大量のエラーに悩まされることになります…
このような場合は最初に述べたとおり、オブジェクトを作ってそのポインタを利用しましょう。
ポインタ型をint型に代入している場合
上記の例は32bitを64bitにしてから32bitに戻すため、キャストの仕様によっては問題なく動きます。
ですが、以下のようにポインタをintにキャストした場合、64bit環境かつintが32bitの場合、
intに入りきらない部分が消滅してしまうため、ポインタに戻しても正しく戻すことができません。
char *str = "test";
int a = (int)str;
const char *string = (char *)(a); // ここで不正なアドレスになる
printf("%s\n", string);
この場合、メモリ破壊などといった特定しにくいエラーを引き起こすため、注意が必要です。
ただし、例によってポインタとintサイズが同一の環境では問題なく動くため、
そうでない環境に対応しようとした場合に悩まされる事になります。
一見するとあまり使わなそうな書き方ですが、フレームワークで用意されているクラスとかでvoid *が無い場合、
クラスのポインタを持たせたいが為に、適当なint型に持たせる…みたいな事をやらかす輩がいますので、
注意が必要です。
またそれ以外にも、ポインタに対して演算を行いたい場合に、int型に変換して計算をする場合があります。
このような場合は、intptr_tかuintptr_tというポインタを扱う整数型が用意されており、
こちらを使うことで32bitや64bitに適したサイズに変換されて扱うことができます。