よく話題にされるヌルポインタについての疑問です。
定数の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) も許しているのは、
一体なぜなのでしょうか。
メリットもあるのでしょうか?
No.3ベストアンサー
- 回答日時:
(1)
> ポインタと数値との内部構造の共通性から
> 結果として正しく動く、という意味でよろしいでしょうか?
その解釈でよいと思います。
私は「NULLは0または0Lと同一構造をもつ無効ポインタ」という解釈で使っています。
(2)
> すなわちNULLをキャスト無しで用いて大丈夫な場合が無くなってしまうのでしょうか?
例えばintel16bit系ではコンパイラが「デフォルトポインタ構造」を提供します。
メモリモデルと呼ばれるもので、コンパイルオプションによって16bitポインタと32bitポインタが指定可能です。
デフォルトポインタ構造を持つ無効ポインタをNULLと表現することは可能になります。
しかし、コンパイルオプションで16bitポインタを指定している場合でも、32bitポインタが必要なことがあり、この場合は適切なキャストが必要です。
他の処理系でも同様な措置によって「多くの場合はNULLが利用可能」と言うことになるでしょう。
しかし、「デフォルトのポインタ構造が可変」という状況はポータビリティーを阻害する可能性があることは間違いありません。
ありがとうございます。
デフォルトポインタ構造の説明をして下さったおかげで、
NULLの限界が正確にはどこにあるのかが理解できました。
ただ単に「(void *)が#defineされているとは限らない」
という点だけでNULLの限界を把握していた状態に比べれば
非常に理解が深まったと感じます。
ANSI C準拠であることしか保証されていない
どんなコンパイラでも意図した通りに動くことを望むならば、
コンパイラがプロトタイプ宣言などを手がかりとして
「これはポインタであり、その内部構造はこうである」
と判断できる場合以外は全てキャストが必要になるわけですね。
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ポインタ」と言う意味です。
ありがとうございます。
ご説明の中で、いくつか確認させていただきたい点があります。
■(1)
> #define NULL 0で充分な処理系(伝統的なintとポインタが相互代入可能な処理系)でも
> #define NULL 0Lな処理系(motorola16ビット系のような処理系)でも
> stdio.hで定義されたNULLで十分な機能を果たします。
の部分ですが、これはコンパイラが
NULLをポインタと判断できず数値として扱った場合でも、
ポインタと数値との内部構造の共通性から
結果として正しく動く、という意味でよろしいでしょうか?
■(2)
ポインタごとに内部構造が異なる場合には
#define NULL ((void *)0) という定義をもってしても、
ポインタだと解釈されることが保証されるだけで、
キャストが不必要になる、という訳ではないのですね。
この点を混同しておりました。
しかしそうだとすると、Intel16ビット系などでは
「拡張的な意味合いを持つNULLポインタ」しか使えない、
すなわちNULLをキャスト無しで用いて大丈夫な場合が無くなってしまうのでしょうか?
以上の2点について、ご面倒でなければ
もう一度ご教授いただけると嬉しいです。
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
ありがとうございます(^^)
> NULLはあくまでもNULLポインタと理解していますが...
のところなのですが、この問題はむしろ
必ずしも「NULL イコール ヌルポインタ」ではない、
すなわち NULL と書いても
ポインタであると解釈されない場合があるという事実に
端を発しているのではないでしょうか?
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- PostgreSQL DBFluteについて質問です。 環境:PostgreSQL java8 前提:webアプリケーショ 1 2022/07/07 00:49
- PHP PHPでCSVを出力するさいに、ループの中で前の行の値を変更したい 3 2022/10/27 17:44
- MySQL エラー 1068 (42000): 複数の主キーが定義されていますエラー 2 2022/11/17 04:36
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- MySQL PHPとMySQLを使った掲示板の作り方 1 2022/06/02 13:00
- その他(コンピューター・テクノロジー) 【Tableau Desktop】文字列から8桁の数字を日付型(yyyyMMdd)として取得 1 2023/07/31 10:17
- C言語・C++・C# C++の標準入力の書き方 6 2023/02/23 23:53
- その他(データベース) Accessのクエリで1フィールドの抽出条件設定をNullでなく全角半角含む空白のみの文字列でない文 1 2023/04/24 15:20
このQ&Aを見た人はこんなQ&Aも見ています
関連するカテゴリからQ&Aを探す
おすすめ情報
このQ&Aを見た人がよく見るQ&A
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
VC++6.0 MFC ダイアログバーを...
-
C言語の関数と配列に関する質問
-
fopne で失敗する原因
-
C言語のポインタに直接アドレス...
-
基本アルゴリズムの『返す』の...
-
C言語: ポインタ
-
【なぜポインタを使うのか】
-
TCHAR文字列内の検索について
-
戻り値で構造体を返すことは可...
-
【VC++2005(CLR)】マルチスレッ...
-
メモリのアドレスからの値の取...
-
ポインタ変数の利用方法
-
VB.NET DLL【API関数(コールバ...
-
VBはCを混乱させる?
-
セグメントエラー
-
LPSTR型の初期化について
-
Windowプログラミング lParam...
-
関数の引数をvoid*でキャストする
-
CopyMemory()をmemcpy()に書き...
-
任意のアドレスの中身を参照し...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
セグメントエラー
-
C言語のポインタに直接アドレス...
-
init関数の意味
-
戻り値で構造体を返すことは可...
-
fopne で失敗する原因
-
C言語の関数と配列に関する質問
-
Run-Time Check Failure #3とい...
-
LPSTR型の初期化について
-
ExcelVBAでのkernel32(64bit)
-
main(int argc,char **argv[])...
-
アプリを32bitから64bit移行
-
ハンドルはポインタか
-
連結リスト 要素の入れ替え
-
C言語でのconstを返す関数
-
Cで作成したDLL関数をVBから呼...
-
NULLとブランクの違い
-
エラーの意味
-
ハンドル、アドレス、ポインタ...
-
DLL<->VB間での受け渡し(文字...
-
【C言語】戻り値が構造体の関数
おすすめ情報