[C言語] NULLは必ず0(番地)ですか?

このQ&Aのポイント
  • C言語でNULLは0(番地)を指していることが保証されているのか疑問です。
  • リスト構造を辿る際のループ継続条件において、NULLを指すかどうかの判定によく利用されるため、正しい書き方を知りたいです。
  • 他の環境でもNULLが必ず0を指すことが保証されているのか、正確な情報を知りたいです。
回答を見る
  • ベストアンサー

[C言語] NULLは必ず0(番地)ですか?

趣味でプログラムを勉強しております。 とある書籍にてリスト構造を辿るソースが以下の様になっていました。 struct List{  省略  struct List *next; }; struct List *p; struct List *header; /* ここでリスト構造作成 */ /* リスト構造を辿る */ for(p = header; p; p=p->next) リストの最終要素のnextはNULLを指しています。 私の環境ではNULLは0(番地?)を指していたので for文のループ継続条件として上記の書き方で動作しました。 質問なのですが、他の環境でもこの書き方で正常に動作するのでしょうか? つまりNULLは必ず0(番地?)を指していることが保証されているのでしょうか。 それとも以下の様に書いた方が良いのでしょうか? for(p = header; p != NULL; p=p->next) よろしければ、ご教示いただけますと幸いです。

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

  • ベストアンサー
回答No.3

これは、割と議論の対象になる話題です。 詳細は、 http://www.kouno.jp/home/c_faq/c5.html#0 が詳しいかと思います。 話は微妙なのですが、 ・Cで、「何も指していないポインタ」の(ポインタとしての)値は、NULL ・それは、必ずしも、0番地を指しているとは限らない ・しかし、「定数0」とは、必ず等しいと評価される。 ・NULL でないどのようなポインタも、「定数0とは等しくない」と評価される。 なので、 for(p = header; p; p=p->next) というのは、標準に合致したCであれば、正しく動作します。 けれど、それは、あくまでも、 ・このfor は、暗黙のうちに、 for(p = header; p != 0 ; p=p->next) と見なされる。 ・有効な(NULL でないポインタは、0に等しくないと評価される) という決まりがあるから、必ずしも、NULL を指しているときのポインタが「0(番地)とは限らない」ということになっています。 ※処理系(コンパイラなど)によりきまります。

siffon9
質問者

お礼

ご回答ありがとうございました。 ご紹介いただいたリンク先の内容は今ひとつ理解できなかったのですが >・Cで、「何も指していないポインタ」の(ポインタとしての)値は、NULL >・それは、必ずしも、0番地を指しているとは限らない >・しかし、「定数0」とは、必ず等しいと評価される。 確認の為 試しに私の環境(MinGW gcc)でstdio.hのNULLの定義を #define NULL ((void *)5) と書き換えた上で、以下のソースを実行してみました。 ---------------------- #include <stdio.h> int main(void){ int *p =NULL; printf("p = %p\n", p); if(p == 0) puts("p == 0 -> true"); else puts("p == 0 -> false"); return 0; } ---------------------- 実行結果 p = 00000005 p == 0 -> false ---------------------- となりましたので「定数0」と等しいと評価はされませんでした。 これは私の考えが安易すぎたせい?それもと処理系依存のせいなのでしょうか?

その他の回答 (8)

回答No.9

そもそも、「ある特定の番地」をポインタにセットする、「標準的な」方法はありません。 規格上では、ポインタはポインタであって、必ずしも整数型による、番地表現と互換性があるわけではないので。 で、「規格」と「実装」という区別になるのですが、 ・ぶっちゃけ、普通に使われるマイコンは、ポインタに、整数定数0を代入すると、0番地を意味するようになるケースが、かなり多いです。 ※組み込みの場合、アセンブラレベルで確認するでしょうし。 ・実は、「整数定数0」と回りくどい言い方をしていますが、int 型の 変数に代入された値 0 は、「整数定数」ではありません。  なので、 int adr = 0; char *p; p = adr; で、p がゼロ番地をポイントする可能性は高いです。 ※ただし、上述したように、整数型をポインタに代入することの結果は、一般的には決定されません。 こんなところでしょうか。

siffon9
質問者

お礼

> そもそも、「ある特定の番地」をポインタにセットする、「標準的な」方法はありません。 > 規格上では、ポインタはポインタであって、必ずしも整数型による、番地表現と互換性があるわけではないので。 ポインタについては、アセンブラの間接アドレッシングの様なものを漠然とイメージしていたのですが、そう単純ではないのですね。 了解しました。 何度もお時間を割いていただきまして本当にありがとうございました。

回答No.8

No.7 一部訂正です。 > だから、通常 > #define NULL 5 と定義されている NULL (つまり、定数0)と等しくなるのです。 だから、通常 #define NULL 0 と定義されている NULL (つまり、定数0)と等しくなるのです。 ですね。

回答No.7

No.3 です。 回答に曖昧な記述がありました。 で #define NULL 5 と定義された NULL は、単なる「定数5」です。 これは、「定数5」なので、0 と等しくなることはありません。 ポインタが何もポイントしていないということを示すポインタも、NULL といいますが、これは、定数0と等しくなります。 だから、通常 #define NULL 5 と定義されている NULL (つまり、定数0)と等しくなるのです。 つまり、#define NULL 5 は、単に、NULL という名前に 5 を割り当てただけです。 この場合、 int *p =NULL; で、p に、「5」のビットイメージが代入されるだけで、それは、NULL のビットパターンではないのです。 No.3 で紹介した、 http://www.kouno.jp/home/c_faq/c5.html#0 の、5.13 に、このあたりの説明があります。 このページをまず、勉強してください。

siffon9
質問者

お礼

何度もご回答ありがとうございます。 #defineによる定義は単に文字列に定数を割り当てたに過ぎないというのはわかりました。 ヌルポインタについて、頭が混乱してきたので整理してみました。 1. 「何処も指していないヌルポインタ」というものが、抽象表現(或いはコンパイラの内部表現)として存在する。   これはどの様に内部表現されているかはプログラマには関知できない(する必要がない) 2. 「何処も指していないヌルポインタ」を見える形に表現しようとすると数値0に変換される。 3. ポインタ変数に数値0を代入すると、内部で「何処も指していないヌルポインタ」に変換される。 4. なので、真偽判定する場所に「何処も指していないヌルポインタ」が代入されているポインタ変数を記述すると、式(ポインタ変数)の評価結果として数値0が返るために「偽」判定となる。 上記は(少なくとも最近のC言語では)保証されている。 という理解でよろしいですか?

siffon9
質問者

補足

申し訳ありません、お礼をした後で疑問が生じました。 ポインタ変数に物理的な0番地を代入する場合はどの様な記述になるのでしょうか? 組み込み系のC言語では存在するような気がします。。。

  • wormhole
  • ベストアンサー率28% (1621/5656)
回答No.6

>確認の為 >試しに私の環境(MinGW gcc)でstdio.hのNULLの定義を > >#define NULL ((void *)5) > >と書き換えた上で、以下のソースを実行してみました。 何を確認しようとされたのでしょうか。 ヘッダファイル内のマクロ定義を書き換えたからといって、その処理系(コンパイラなど)が内部に保持しているNULLの定義が書き換わるわけじゃないですけど。

siffon9
質問者

お礼

やはり私の考えが安易すぎたということですね。 ご回答ありがとうございました。

回答No.5

No.3 です。 for(p = header; p; p=p->next) というコーディングですが、やっちゃいけないというほどのものではないです。 少なくともCの世界では、「イディオム」に近いですから。 たとえば、 for(p = header; p != NULL; p=p->next) という書き方は、「p が NULL じゃない間」というのを主張しているわけです。 でも、 for(p = header; p; p=p->next) は、意味的には、「リストの要素が尽きるまで」ということを主張しているわけです。 こういう風に、むしろ、「言いたいことを言う」コーディングでもあるわけです。 同じように、 while(str[i++]) // 文字列が尽きるまで if(ifalpha(c)) // アルファベットなら のように、「ゼロと比較する」よりも、抽象的な意味を主張することができます。 Cでは、こういう使い方を意図している場面もあって、こういう表現は、割と、一般的です。 (少なくとも、一字一句プログラムを解釈する場面を超えれば)

siffon9
質問者

お礼

再びのご回答ありがとうございました。 > while(str[i++]) // 文字列が尽きるまで > if(ifalpha(c)) // アルファベットなら これらの例のように、戻り値が数値であり、そのまま真偽値として使用できると理解している場合には問題なかったのですが > for(p = header; p; p=p->next) > は、意味的には、「リストの要素が尽きるまで」ということを主張しているわけです。 > こういう風に、むしろ、「言いたいことを言う」コーディングでもあるわけです。 こちら場合には、NULLが真偽値として、そのまま使用できるのかが疑問としてあった為に質問させていただいた次第です。 NULLが真偽値の「偽」として評価されると理解できていれば、この使用方法も有りだと思いました。 ただ他の方のお礼にも書きましたとおり、この書き方では後日に自分で書いたソースをみて戸惑う可能性も充分にあると思いますので、自分で書く場合は明確に理解できる表現で書きたいと思います。

回答No.4

回答としては、問題なく動作しますが、文法上やめるべきでしょう。 C言語では真偽値(true/false)がゼロとの比較となり、 かつNULLはゼロとなっているので、以下の書き方でどの様な処理系でも問題なく動作します。 for(p = header; p; p=p->next) しかし、コーディングルール(プログラムを書くときのルール)としては、 判断の部分に値を入れるのはナンセンスですね。 つまり書く時のミスの原因や、後で他の人が見た時に意味が分かりづらいという事です。 if( 判断 ) { } for( : 判断 : ) { } while( 判断 ) { } の様に判断を入れるところには == や > 、<というようにしっかりと 明示的に判断した結果を入れるべきです。 for(p = header; p!=NULL ; p=p->next) 以前は、メモリ容量の都合や、ルールが発展してなくて判断値と変数値を ごちゃまぜにしたプログラムが存在しましたが、 これからは、他の人が見ることや、後で自分でわからなくならないように ちゃんと動くだけではなくて、見やすい(可読性の高い)プログラムを作るように するべきだと考えられています。

siffon9
質問者

お礼

ご回答ありがとうございました。 > しかし、コーディングルール(プログラムを書くときのルール)としては、 > 判断の部分に値を入れるのはナンセンスですね。 > つまり書く時のミスの原因や、後で他の人が見た時に意味が分かりづらいという事です。 仰るとおりだと思います。 自分でプログラムを書く場合にも見やすい書き方になるように留意したいと思います。

  • tsunji
  • ベストアンサー率20% (196/958)
回答No.2

バグで苦しみたくないなら、変数は使う前に初期化しておくべきでしょう。 プログラムの起動時、初期化せずに使えるかもしれませんが、 プログラムの仕様によっては、リストを再作成するなどの処理が発生した場合、 正常に動作しなくなりますよ。

siffon9
質問者

お礼

ご回答ありがとうございました。

  • bardfish
  • ベストアンサー率28% (5029/17765)
回答No.1

http://ja.wikipedia.org/wiki/Null#C.E8.A8.80.E8.AA.9E.E3.81.AE.E3.83.8C.E3.83.AB.E3.83.9D.E3.82.A4.E3.83.B3.E3.82.BF 難しく考えてはいけません。 リンク先にも書かれていますが「なにもない」という捉え方で十分です。 配列やグループの場合、これ以上何もないという意味でNULLを使用します。 0(ゼロ)で理解してしまうと、データとしてゼロ(数値のゼロではない)を使いたいときに混乱しますよね。データとしてのゼロとNULLのゼロは取り扱いが異なります。 >他の環境でもこの書き方で正常に動作するのでしょうか? それは使用するコンパイラ次第。 よくわからないんだったら曖昧な書き方をするんじゃなくて、厳密な書き方を心がけるようにしましょう。 厳密なコーディングをしておけば、他人が見た時にナニをしたいかすぐに理解できます。が、曖昧な書き方だとコンパイラや環境の解釈の違いを調べなくてはいけませんからタイヘンです。

siffon9
質問者

お礼

NULLをポインタの値と考えた場合には、たしかに「なにもない」という捉え方が適切だと思います。でも真偽値を書くべき場所に書かれた場合は、「なにもない」よりは0(番地)と捉えた方がわかり易いと私は思いました。 ご紹介いただきましたリンク先を拝見しますと、最近の環境では0または(void *)0 と定義させているということですので、質問の最初のforの書き方でも問題なさそうということですね。 ちなみに私の環境(MinGW gcc)でも後者の定義がなされていました。 とはいえ、わかり易い書き方をすべきということには同意致しますので、私も後者の様な書き方をしようと思います。 ご回答ありがとうございました。

関連するQ&A

  • C言語の構造体についてなんですが。

    struct LIST {     struct Num* number;     struct LIST* next;/* 次の要素へのポインタ */ }*root; struct Num{     int a;     struct Num* next; /* 次の要素へのポインタ */ }*numroot; と構造体を定義したときに、 main(){     struct LIST *p;     for(p = root; p != NULL; p = p->next) ; } とすれば、pの先頭からNULLまでを参照していくことは分かるんですが、pのnumberの先頭からNULLまでの参照方法(プログラムのfor文の記述方法)がイマイチわかりません。つまり、構造体の構造体をどのように参照するかということです。 これを実現したい理由は、構造体内での数の格納を配列(固定長)ではなく可変長で格納したいからです。 分かる方は解答をお願いします。

  • C言語 リスト

    (1) /* list.c */ #include <stdio.h> #include <stdlib.h> struct node { int num; struct node *next; }; main() { struct node *head, *p; int x; head = NULL; while(scanf("%d",&x) != EOF) { p = (struct node *)malloc(sizeof(struct node)); p->num = x; p->next = head; head = p; } // リストの要素のnumの値を先頭から順に表示する p=head; while(p!=NULL) { printf("%d\n" ,p->num); p = p->next; } } (2) struct node *q; q = head; while(q->next != NULL) q = q->next; (1)を(2)を使い新しいノードをリストの最後に追加するようにしたいのですが どう書いたら良いのか教えていただきたいです

  • 連結リストについて

    連結リストについて アルゴリズムの本を買って勉強しているのですが、わからない箇所がありまして。 構造体宣言で (1)struct CELL { struct CELL *next; int val; }; (2)struct CELL { struct CELL *next; int val; }*header; 順番をたどって行く書き方で struct CELL *p; for (p=header; p!=NULL; p=p->next) printf("%d\n", p->value); なぜこれでp->valueの値が変動していくのですか? それと上記の(1)の書き方でこのforを回すとき、headerはどのように定義すればよいのですか? よろしくお願いします。

  • C言語の問題について

    #include <stdio.h> #define NMAX 20 struct node_tag { ??????? }; int main(void) { ??????? struct node_tag *p; int i; for (???????) { ??????? } p = ???????; while (p!=NULL){ printf("%d %s\n", ???????, ???????); p = ??????? } return 0; } 「 日付(整数) と 曜日名(文字) と 次の要素を指すポインタ 」を要素に持つ自己参照構造体 struct note_tag を定義して、この構造体を利用して線形リストを作成し,日付と曜日を表示させるプログラムを作成したいのですが?がわからなくて困ってます・・・どうか教えてください

  • C言語のプログラム

    以下のプログラムはハッシュテーブルを用いて文字列を探すプログラムなのですが、コンパイル時にセグメントエラーとなってしまいます。プログラム中に誤った箇所があれば教えて頂きたいです。 #include <stdio.h> #include <stdlib.h> #include <string.h> #define HASHSIZE 10 #define MAX_LEN 64 #define N_WORDS 4 struct list { char word [MAX_LEN]; struct list *next; }; struct list *hash_table[HASHSIZE]; char colors[N_WORDS][MAX_LEN] = {"red", "blue", "green", "yellow"}; void my_strcpy(char* a, const char* b) { int i = 0; while(*b != '\0'){ *(a+i) = *(b+i); i++; } *(a+i) = '\0'; } int hash(char *key) { int hashval = 0; while (*key != '\0') { hashval += *key; key++; } return (hashval % HASHSIZE); } int find_word (char *key) { struct list *p; for (p = hash_table[hash(key)]; p != NULL; p++) if (strcmp(key, p->word) == 0) return 1; return 0; } void init_hash_table() { int i, hashvalue; struct list *p, *q; for (i = 0; i < HASHSIZE; i++) { hash_table[i] = NULL; } for (i = 0; i < N_WORDS; i++) { if ((find_word(colors[i])) == 0){ p = (struct list *)malloc(sizeof(struct list)); my_strcpy(p->word, colors[i]); hashvalue = hash(colors[i]); if (hash_table[hashvalue] = NULL) { hash_table[hashvalue] = p; p->next=NULL; } else { q = hash_table[hashvalue]; while (q->next != NULL) q = q->next; q->next = p; p->next=NULL; } } } } void main(void) { init_hash_table(); printf("result = %d\n", find_word("red")); }

  • LinkList使い方の意味がわかりません。。。。。

    LinkList使い方の意味がわかりません。。。。。 とあるサイトで以下のソースを目にしました。 ※以下参照 struct LIST * newList( struct LIST ** pstart){ struct LIST *p, *new; p = *pstart; new = (struct LIST *) malloc(sizeof(struct LIST)); new->next = NULL; if(p!=NULL){ /* リストに要素が存在するとき */ while(p->next!=NULL){ /* リストの末尾を探す */ p=p->next; } p->next = new; /* リストの末尾につなげる */ }else{ *pstart = new; /* 最初の要素の場所を *pstart に */ } return new; } そもそもここでnew->next = NULL;やってますけどmallocされた時点で実値は格納されていない と思うので必然的にNULLポインタになると思っていますが。。。(認識違いますか?) 誰か上記のこと含めLinkListに詳しい方使用方法(考え方)をご教授ください。

  • 構造体のポインタにNULLが入らない

    typedef struct tag{ int number; char name[10]; struct tag *next; }DATA; という構造体があって、 DATA *p; と宣言し、 p->next == NULL; とすることはできないんですか? セグメンテーション違反になってしまうのですが。 pが指すnextにNULLを入れるにはどうしたらいいのでしょうか?

  • C言語の自己参照型プログラムについて

    #include <stdio.h> #include <string.h> #include <stdlib.h> struct list { int key; /* キー */ char name[20]; /* 名前 */ struct list *next; /* 次のデータへのポインタ */ }; struct list *add_list(int key, char *name, struct list *head); void show_list(struct list *p); void free_list(struct list *p); int main(void) { struct list *head; /* 先頭ポインタ */ char name[20]; int key = 0; head = NULL; /* 先頭ポインタにNULLを設定 */ printf("キーと名前(MAX:19文字)を入力(終了:CTRL+Z)\n"); while (scanf("%d %s", &key, name) != EOF) { /* リストにデータを登録 */ head = add_list(key, name, head); } /* リストの表示 */ show_list(head); /* リストの開放 */ free_list(head); return 0; } /*** リストにデータを登録 ***/ struct list *add_list(int key, char *name, struct list *head) { struct list *p; /* 記憶領域の確保 */ if ((p = (struct list *) malloc(sizeof(struct list))) == NULL) { printf("malloc error\n"); exit(EXIT_FAILURE); } /* リストにデータを登録 */ p->key = key; strcpy(p->name, name); /* ポインタのつなぎ換え */ p->next = head; /* 今までの先頭ポインタを次ポインタに */ head = p; /* 新たな領域を先頭ポインタに */ return head; } /*** リストの表示 ***/ void show_list(struct list *p) { while (p != NULL) { /* 次ポインタがNULLまで処理 */ printf("%3d %s\n", p->key, p->name); p = p->next; } } /*** リストの開放 ***/ void free_list(struct list *p) { struct list *p2; while (p != NULL) { /* 次ポインタがNULLまで処理 */ p2 = p->next; free(p); p = p2; } } これを実行すると、 新しく入力された順にリストが表示されます。 そうではなく、キーの昇順に表示したいです。 どなたかそのように実行できるようにプログラムを書き換えてくれませんか? 図々しいですがよろしくお願いいたします。m(_ _)m

  • MIPSアセンブラ言語について

    #include NULL 0 struct list{ struct list +next; int value; }; int sumvalue(sturuct list *head){ struct list *cur=NULL; int sum=0; for(cur = head; cur !=NULL; cur=cur->next){ sum += cur->value; } return sum; } このC言語で書かれた関数をMIPSアセンブラで記述するとどうなるのでしょうか?ポインタで混乱してます。

  • 線形リスト

    こんにちわ。今、大学で線形リストの勉強してるのですがよくわかりません。 ↓のプログラムで (1)10個のIDを受け取って受け取った順にリストを作るプログラム(各要素の値のアドレスとそのnextの値を表示してリストになっていることを確認する。) (2) (1)のプログラムを拡張して、リストを作ったあと、1から10までの間の整数nを入力として受け取り、リストのn番目の要素のIDを表示プログラムを書け。 の2つのことをしたいんですけど、どのようにすればいいのですか?どなたか回答おねがいします。 #include<stdio.h> #include<stdlib.h> struct list{ int ID; struct list *next; }; int main(int argc, char* argv[]) { struct list *top; top=(struct list *)malloc(sizeof(struct list)); //struct list 型の大きさの領域を1つ確保 //topがそこを指している状態にする //printf("IDを10個入力してください"); if(top == NULL){ //メモリが足りないと値がNULLになってします printf("メモリは確保できません\n"); exit(1); //強制終了。プログラムはココで終わってしまいます } top->next=NULL; top->ID=1; //ポインタtopが指している構造体のIDの値を変えている top->next=(struct list*)malloc(sizeof(struct list)); //もう1つ作ります //top->next==NULL (メモリ確保に失敗)の処理は省略 (top->next)->next=NULL; (top->next)->ID=5; //これで下図のような状態になります; return 0; }

専門家に質問してみよう