- ベストアンサー
#define NULL ((void *)0) の弊害
よく話題にされるヌルポインタについての疑問です。 定数の0は、それがポインタと解されるべき文脈では ヌルポインタに読み替えられますが、 可変長引数のようにポインタであることがコンパイラには判断できない文脈では、 明示的にキャストしてやらなければなりません。 このとき、#define NULL 0 と定義されている処理系では、 NULLを使っても定数の0を書いたのと全く同じであり、 上のような場合におけるキャストの必要性からは逃れられません。 しかし、たまたま自分の処理系で #define NULL ((void *)0) と定義されていれば、 キャストを行わなくてもNULLを使うことによって正しく動いてしまいます。 ということは、#define NULL ((void *)0) と定義された処理系しか 使ったことの無いプログラマは、 「NULLを使うこと自体が、これはポインタだよという意志表示になる」 と錯覚してしまう危険性をはらんでいることになります。 この人の書いた「NULLを使い、必要なキャストを省略しているソース」を、 #define NULL 0 と定義された処理系でコンパイルすると 正しく動作しない可能性があります。 こういう弊害があるにもかかわらず、 ANSI Cでは #define NULL 0 のほかに #define NULL ((void *)0) も許しているのは、 一体なぜなのでしょうか。 メリットもあるのでしょうか?
- みんなの回答 (3)
- 専門家の回答
質問者が選んだベストアンサー
(1) > ポインタと数値との内部構造の共通性から > 結果として正しく動く、という意味でよろしいでしょうか? その解釈でよいと思います。 私は「NULLは0または0Lと同一構造をもつ無効ポインタ」という解釈で使っています。 (2) > すなわちNULLをキャスト無しで用いて大丈夫な場合が無くなってしまうのでしょうか? 例えばintel16bit系ではコンパイラが「デフォルトポインタ構造」を提供します。 メモリモデルと呼ばれるもので、コンパイルオプションによって16bitポインタと32bitポインタが指定可能です。 デフォルトポインタ構造を持つ無効ポインタをNULLと表現することは可能になります。 しかし、コンパイルオプションで16bitポインタを指定している場合でも、32bitポインタが必要なことがあり、この場合は適切なキャストが必要です。 他の処理系でも同様な措置によって「多くの場合はNULLが利用可能」と言うことになるでしょう。 しかし、「デフォルトのポインタ構造が可変」という状況はポータビリティーを阻害する可能性があることは間違いありません。
その他の回答 (2)
- toysmith
- ベストアンサー率37% (570/1525)
> こういう弊害があるにもかかわらず、 > ANSI Cでは #define NULL 0 のほかに > #define NULL ((void *)0) も許しているのは、 > 一体なぜなのでしょうか。 > メリットもあるのでしょうか? NULLは処理系に対する依存度の高いstdio.hで定義されるべきものであり、#define NULL 0で充分な処理系(伝統的なintとポインタが相互代入可能な処理系)でも#define NULL 0Lな処理系(motorola16ビット系のような処理系)でもstdio.hで定義されたNULLで十分な機能を果たします。 しかし、ポインタごとに内部構造が違うような処理系(intel16ビット系のような処理系)では#define NULL ((void *)0)であっても充分ではありません。 ポインタ構造がポインタ値ごとに違う可能性があるためfar *とnear *の2種類のポインタを明示的に使い分ける必要があるためです。 結果、NULL自体は伝統的な「標準NULLポインタ」としての利用のみが想定され、「拡張的な意味合いを持つNULLポインタ」に関しては「適切なキャストを伴う0をプログラマの責任で使用する」と言うことになっているのではないでしょうか。 ここでいう標準的なNULLポインタとは「intまたはlongと相互代入可能なことが保証された単一のポインタ構造に基づくNULLポインタ」と言う意味です。
お礼
ありがとうございます。 ご説明の中で、いくつか確認させていただきたい点があります。 ■(1) > #define NULL 0で充分な処理系(伝統的なintとポインタが相互代入可能な処理系)でも > #define NULL 0Lな処理系(motorola16ビット系のような処理系)でも > stdio.hで定義されたNULLで十分な機能を果たします。 の部分ですが、これはコンパイラが NULLをポインタと判断できず数値として扱った場合でも、 ポインタと数値との内部構造の共通性から 結果として正しく動く、という意味でよろしいでしょうか? ■(2) ポインタごとに内部構造が異なる場合には #define NULL ((void *)0) という定義をもってしても、 ポインタだと解釈されることが保証されるだけで、 キャストが不必要になる、という訳ではないのですね。 この点を混同しておりました。 しかしそうだとすると、Intel16ビット系などでは 「拡張的な意味合いを持つNULLポインタ」しか使えない、 すなわちNULLをキャスト無しで用いて大丈夫な場合が無くなってしまうのでしょうか? 以上の2点について、ご面倒でなければ もう一度ご教授いただけると嬉しいです。
- MovingWalk
- ベストアンサー率43% (2233/5098)
NULLはあくまでもNULLポインタと理解していますが... >一体なぜなのでしょうか。メリットもあるのでしょうか? NULL を 数値型の'0'と混同しているプログラマーの救済措置なんでしょうかね~。 NULLについてはこちらにいろいろと書かれています。 http://www.catnet.ne.jp/kouno/c_faq/c5.html NULLは「領域確保の失敗」とか、まだ「何も指していない」のような 意味を持つ というのが正しい解釈だと思っていますけど...
お礼
ありがとうございます(^^) > NULLはあくまでもNULLポインタと理解していますが... のところなのですが、この問題はむしろ 必ずしも「NULL イコール ヌルポインタ」ではない、 すなわち NULL と書いても ポインタであると解釈されない場合があるという事実に 端を発しているのではないでしょうか?
お礼
ありがとうございます。 デフォルトポインタ構造の説明をして下さったおかげで、 NULLの限界が正確にはどこにあるのかが理解できました。 ただ単に「(void *)が#defineされているとは限らない」 という点だけでNULLの限界を把握していた状態に比べれば 非常に理解が深まったと感じます。 ANSI C準拠であることしか保証されていない どんなコンパイラでも意図した通りに動くことを望むならば、 コンパイラがプロトタイプ宣言などを手がかりとして 「これはポインタであり、その内部構造はこうである」 と判断できる場合以外は全てキャストが必要になるわけですね。