ポインタのキャストはC言語でよく行われる操作で、一般的なインターフェースの利用や、例えばvoid*を介してメモリを扱ったり、汎用データ構造を実装することを可能にします。しかし、ポインタタイプのキャストには一定のリスクが伴い、厳格な基準に従う必要があります。
void* はCにおいてアドレスを保持しますが、指し示す内容の型を認識しません。他の任意のポインタ(例:int*, char*, struct mytype*)は、明示的(または暗黙的)にvoid*にキャストでき、情報の損失なしに元に戻すことができます("縮小"が発生しない限り)。void*以外に)キャストする場合、そのアドレスに実際に互換性のある型の値が存在することを確認する必要があります。void process(void *data) { int *arr = (int*)data; // arrをint型の配列として使用 } int main() { double x = 10; process(&x); // 危険:double*をint*にキャストしている、UB }
"任意のポインタは安全にvoid*およびその逆にキャストできるか?"
多くの人は"はい"と答えます — なぜならCの標準は任意のオブジェクトポインタをvoid*にキャストしても損失がないことを保証しているからです。しかし、重要なことは、非オブジェクトポインタ(例えば、関数ポインタ)をvoid*にキャストしたり、異なるアーキテクチャ(ポインタのサイズが関数とデータで異なる)を混ぜると未定義の動作を引き起こすことです。
void foo() {} void *p = (void*)foo; // UB! 関数ポインタはこのように変換することはできない
ストーリー
クロスプラットフォームのデータ処理サブシステムのプロジェクトでは、構造体のポインタを
void*にキャストしてから元の型に戻すハンドラを使用していました。int*とdouble*が異なるアライメントを持つアーキテクチャに移行した際、void*を不正な型にキャストしたために"bus error"(異常終了)が発生しました。
ストーリー
埋め込みプロジェクトでは、プログラマが汎用インターフェースを持つリングバッファを
void*で実装しましたが、アライメント要件の厳格さを忘れ(char配列のためにメモリを確保し、int*として渡した)、いくつかのプラットフォームではデータが"ハードウェアに理解されなくなり"、読み取りエラーや不安定な動作が発生しました。
ストーリー
動的コレクションではアドレスを
void*として保持しましたが、関数ポインタをvoid*にキャストできないことを忘れていました。イベントハンドラ(callback)をそのフィールドを介して保持しようとして試みると、一部のプラットフォームでのみ失敗し、エラーを捕らえるのが非常に難しい状況に陥りました。