• ベストアンサー

#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) も許しているのは、 一体なぜなのでしょうか。 メリットもあるのでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • toysmith
  • ベストアンサー率37% (570/1525)
回答No.3

(1) > ポインタと数値との内部構造の共通性から > 結果として正しく動く、という意味でよろしいでしょうか? その解釈でよいと思います。 私は「NULLは0または0Lと同一構造をもつ無効ポインタ」という解釈で使っています。 (2) > すなわちNULLをキャスト無しで用いて大丈夫な場合が無くなってしまうのでしょうか? 例えばintel16bit系ではコンパイラが「デフォルトポインタ構造」を提供します。 メモリモデルと呼ばれるもので、コンパイルオプションによって16bitポインタと32bitポインタが指定可能です。 デフォルトポインタ構造を持つ無効ポインタをNULLと表現することは可能になります。 しかし、コンパイルオプションで16bitポインタを指定している場合でも、32bitポインタが必要なことがあり、この場合は適切なキャストが必要です。 他の処理系でも同様な措置によって「多くの場合はNULLが利用可能」と言うことになるでしょう。 しかし、「デフォルトのポインタ構造が可変」という状況はポータビリティーを阻害する可能性があることは間違いありません。

zabuzaburo
質問者

お礼

ありがとうございます。 デフォルトポインタ構造の説明をして下さったおかげで、 NULLの限界が正確にはどこにあるのかが理解できました。 ただ単に「(void *)が#defineされているとは限らない」 という点だけでNULLの限界を把握していた状態に比べれば 非常に理解が深まったと感じます。 ANSI C準拠であることしか保証されていない どんなコンパイラでも意図した通りに動くことを望むならば、 コンパイラがプロトタイプ宣言などを手がかりとして 「これはポインタであり、その内部構造はこうである」 と判断できる場合以外は全てキャストが必要になるわけですね。

その他の回答 (2)

  • toysmith
  • ベストアンサー率37% (570/1525)
回答No.2

> こういう弊害があるにもかかわらず、 > 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ポインタ」と言う意味です。

zabuzaburo
質問者

お礼

ありがとうございます。 ご説明の中で、いくつか確認させていただきたい点があります。 ■(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)
回答No.1

NULLはあくまでもNULLポインタと理解していますが... >一体なぜなのでしょうか。メリットもあるのでしょうか? NULL を 数値型の'0'と混同しているプログラマーの救済措置なんでしょうかね~。 NULLについてはこちらにいろいろと書かれています。 http://www.catnet.ne.jp/kouno/c_faq/c5.html NULLは「領域確保の失敗」とか、まだ「何も指していない」のような 意味を持つ というのが正しい解釈だと思っていますけど...

参考URL:
http://www.catnet.ne.jp/kouno/c_faq/c5.html
zabuzaburo
質問者

お礼

ありがとうございます(^^) > NULLはあくまでもNULLポインタと理解していますが... のところなのですが、この問題はむしろ 必ずしも「NULL イコール ヌルポインタ」ではない、 すなわち NULL と書いても ポインタであると解釈されない場合があるという事実に 端を発しているのではないでしょうか?

関連するQ&A

  • NULLポインタが0でない処理系とは?

    http://okwave.jp/qa/q7458540.html にて、 > C言語の規格上、0はNULLポインタとして扱われますが > NULLポインタは0とは限らないので(0でない処理系もあります)。 と他の回答者が答えていたのが妙に引っかかりました。 後学のために知っておきたいのですが、 どの処理系だとNULLポインタが0ではないのかどなたかご存じないですか? 気になって、C99の仕様書らしいものを検索で見つけたのですが、 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf これによると、6.3.2.3 Pointers の 3節 にて、 > An integer constant expression with the value 0, > or such an expression cast to type void *, is called a null pointer constant. とあり、7.17 Common definitions <stddef.h> の 3節 でも、 > The macros are > NULL > which expands to an implementation-defined null pointer constant; (略) とあるので、NULLポインタを表す定数は0か(void*)0だと思っていたのですが。 言い換えると、 struct foo { char* bar; }; と構造体が定義されている時、 struct foo baz; bzero(&baz, sizeof(baz)); すればbaz.barはNULLポインタ定数に初期化されると思っていたのですが、 これだとダメな処理系はどういう処理系で、どんな用途で使うのでしょうか? おそらく、0でない値にした目的や歴史的経緯があるのではないかと推測するのですが。

  • defineで定数が置き換えられない?(C言語)

    #defineについて質問です。 #defineは、ソースコード内にこの文字を見つけたら、コンパイルする前にこの文字をこの定数に置き換えて、というプリプロセッサですよね?だから、printfで#defineで定義した定数を出力する場合で、変換指定が必要ない場合は #include <stdio.h> #define DEF 100 void main(){ printf("#defineで定義された定数はDEFです"); } でもいいと思うんです。ですがこのソースコードは間違いで、実行結果は #defineで定義された定数はDEFです(←置き換えられてない) となってしまします。プリプロセッサだけ実行しても、DEFは100に置き換えられずそのままです。printfで#defineで定義した定数を出力させたい場合は書式指定をしなければなりません。なぜ、このような場合は#defineで定義した定数は置き換えられないのでしょうか?回答よろしくお願いします。

  • C言語のvoid型ポインタを使いたいのですが…

    C言語のvoid型ポインタを使いたいのですが… 関数の引数として、void型ポインタを使おうと思ったのですが、内部でどのように処理すればいいのかわかりません キャストすれば問題なく使えるとのことですが、どの型でキャストするのかをどのように判断するのかがわかりません 具体的には、画像処理で画像の構造体をいくつか作ったのですが、それぞれの構造体ごとに関数を書くと関数が多くなるので、void型ポインタでまとめてつくろうとしています どのように型の判断を行えばいいのかを教えてください

  • void型のポインタで構造体の参照

    void型のポインタで構造体や共用体を参照することはできますか? void *p=&kou; struct KOU kou; (struct KOU*)kou.name="名前"; のようにして構造体を参照しようとしたのですが、「左側が構造体又は共用体ではありません。」と出ます。型キャストはコンパイラに型を知らせるだけのものなのでコンパイラが構造体の型を知ることができない、ということでしょうか?void型のポインタを使って構造体(共用体)を参照することはできますか?回答よろしくお願いします。

  • 「NULLポインタ」と「演算の結果としてのアドレス0」との比較

    ものすごく基本的な疑問です。。。 「C言語FAQ日本語訳」http://www.kouno.jp/home/c_faq/ ここの「05.ヌルポインター」を見ると以下のような意味の記述があります。 「NULLポインタは他のどんなポインタの値とも区別可能で、有効なポインタと比較しても等しくなる事はない」 「ポインタを書くべき場所に書かれた定数0はコンパイル時にNULLポインタに変換される」 そこで以下のプログラムを Borland C++ 5.5 for Win32 でコンパイル・実行してみたところ、 p1 == p2 とりました。これって変ですよね? p1 は明示的に定数0で初期化しているのでNULLポインタですが、p2 は演算の結果としてアドレス0番地を指しているので、NULLポインタでは無いですよね? これはコンパイラが間違っていると思って良いのでしょうか? #include <stdio.h> int main() { char *p1,*p2; p1 = 0; p2 = (char *)1; p2--; if(p1 == p2){ printf("p1 == p2"); } else{ printf("p1 != p2"); } return 0; }

  • Cで言うNULLは「C++では0をリテラルとする」みたいな事を、

    Cで言うNULLは「C++では0をリテラルとする」みたいな事を、 どこかで昔、読んだのですが今でもそうでしょうか? (Cの場合、厳密にはdefine定義でNULLとしている(た)のでしょうが…) 最近のCとC++の言語仕様に詳しい方、お願いします。 気分的にはC++の場合、自分はポインタ初期化を0で行う事が好きなので、 他の人はどうしてるんだろうと、気になった所から生まれた疑問です。 //例:↓ClassXのコンストラクタの実装 ClassX::ClassX(void) :mPtr(0) { } }

  • C言語:void*の指す型の比較をする方法

    C言語において、Java言語で言うところのinstanceof演算子の様な、方の比較をする演算子、もしくは、その方法をご存知でしたら教えていただきたく思います。つまり、   void型のポインタ変数x   void *x = (何とかのアドレス); となっている時、xの指す型・構造体を知りたいのです。 問題の解決方法・アドヴァイスを宜しくお願いします。 私は現在 void *function(..., ...); という関数を作り、それは処理に応じて,A構造体へのポインタ、B構造体へのポインタ又は、NULLを返します。そして、 void *tmp = function(..., ...); を作りました。tmpがAを指している時、Bを指している時、NULLとなっている時、それぞれに合わせて異なった処理をしたく思っています。そのためには、tmpがどの構造体又は、NULLを指しているのかが分からないといけません。その方法が分からず困っています。

  • 警告をださずにポインタにnullを渡す方法は?

    こんにちは。 ポインタ型の引数にNULLを渡してやりたいのですが 以下のような警告がでてしまいます。 test.c: In function 'test_': test.c:14: 警告: passing argument 2 of 'test' makes pointer from integer without a cast 問題の箇所は以下のように書いてます。 【test.c】 ~省略~ /** プロトタイプ **/ 7 void aaa(int,int*,int*); ~省略~ 14 aaa(0,NULL,&tv); NULLを渡したい場合、警告をださないように スマートに記述するにはどうしたらいいでしょうか? よろしくお願いします。

  • void*の型キャストについて質問です

    VisualStudio 2008 を使ってOpenCVを勉強中なのですが、 … IplImage *dst; … cvRelease(&dst); をすると、 error C2664: 'cvRelease' : 1 番目の引数を 'IplImage **__w64 ' から 'void **' に変換できません。(新しい機能 ; ヘルプを参照) と出ます。 確か、void*はどの型からでも明示型キャストは必要なかったと思うのですが、これはVisual Studio 2008 の仕様ですか?

  • ポインタと()について

    ((void*)0)がNULLらしいのですが、(void*)0とキャストするだけではダメなのでしょうか?ポインタや配列関係?で()を厳重に多くしている場合をよく見ますが、何か理由があるのでしょうか。