プロが教えるわが家の防犯対策術!

趣味でプログラムを勉強しております。

とある書籍にてリスト構造を辿るソースが以下の様になっていました。

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)


よろしければ、ご教示いただけますと幸いです。

A 回答 (9件)

これは、割と議論の対象になる話題です。



詳細は、
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(番地)とは限らない」ということになっています。
※処理系(コンパイラなど)によりきまります。
    • good
    • 0
この回答へのお礼

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

ご紹介いただいたリンク先の内容は今ひとつ理解できなかったのですが

>・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」と等しいと評価はされませんでした。


これは私の考えが安易すぎたせい?それもと処理系依存のせいなのでしょうか?

お礼日時:2014/09/30 20:37

そもそも、「ある特定の番地」をポインタにセットする、「標準的な」方法はありません。


規格上では、ポインタはポインタであって、必ずしも整数型による、番地表現と互換性があるわけではないので。

で、「規格」と「実装」という区別になるのですが、

・ぶっちゃけ、普通に使われるマイコンは、ポインタに、整数定数0を代入すると、0番地を意味するようになるケースが、かなり多いです。
※組み込みの場合、アセンブラレベルで確認するでしょうし。

・実は、「整数定数0」と回りくどい言い方をしていますが、int 型の 変数に代入された値 0 は、「整数定数」ではありません。
 なので、
int adr = 0;
char *p;
p = adr;
で、p がゼロ番地をポイントする可能性は高いです。
※ただし、上述したように、整数型をポインタに代入することの結果は、一般的には決定されません。

こんなところでしょうか。
    • good
    • 0
この回答へのお礼

> そもそも、「ある特定の番地」をポインタにセットする、「標準的な」方法はありません。
> 規格上では、ポインタはポインタであって、必ずしも整数型による、番地表現と互換性があるわけではないので。

ポインタについては、アセンブラの間接アドレッシングの様なものを漠然とイメージしていたのですが、そう単純ではないのですね。
了解しました。

何度もお時間を割いていただきまして本当にありがとうございました。

お礼日時:2014/10/01 17:57

No.7 一部訂正です。



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

だから、通常
#define NULL 0 と定義されている NULL (つまり、定数0)と等しくなるのです。

ですね。
    • good
    • 0

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 に、このあたりの説明があります。

このページをまず、勉強してください。

この回答への補足

申し訳ありません、お礼をした後で疑問が生じました。

ポインタ変数に物理的な0番地を代入する場合はどの様な記述になるのでしょうか?
組み込み系のC言語では存在するような気がします。。。

補足日時:2014/09/30 23:20
    • good
    • 0
この回答へのお礼

何度もご回答ありがとうございます。

#defineによる定義は単に文字列に定数を割り当てたに過ぎないというのはわかりました。


ヌルポインタについて、頭が混乱してきたので整理してみました。

1. 「何処も指していないヌルポインタ」というものが、抽象表現(或いはコンパイラの内部表現)として存在する。
  これはどの様に内部表現されているかはプログラマには関知できない(する必要がない)

2. 「何処も指していないヌルポインタ」を見える形に表現しようとすると数値0に変換される。

3. ポインタ変数に数値0を代入すると、内部で「何処も指していないヌルポインタ」に変換される。


4. なので、真偽判定する場所に「何処も指していないヌルポインタ」が代入されているポインタ変数を記述すると、式(ポインタ変数)の評価結果として数値0が返るために「偽」判定となる。


上記は(少なくとも最近のC言語では)保証されている。


という理解でよろしいですか?

お礼日時:2014/09/30 23:12

>確認の為


>試しに私の環境(MinGW gcc)でstdio.hのNULLの定義を
>
>#define NULL ((void *)5)
>
>と書き換えた上で、以下のソースを実行してみました。

何を確認しようとされたのでしょうか。
ヘッダファイル内のマクロ定義を書き換えたからといって、その処理系(コンパイラなど)が内部に保持しているNULLの定義が書き換わるわけじゃないですけど。
    • good
    • 0
この回答へのお礼

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

お礼日時:2014/09/30 22:03

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では、こういう使い方を意図している場面もあって、こういう表現は、割と、一般的です。
(少なくとも、一字一句プログラムを解釈する場面を超えれば)
    • good
    • 0
この回答へのお礼

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

> while(str[i++]) // 文字列が尽きるまで
> if(ifalpha(c)) // アルファベットなら

これらの例のように、戻り値が数値であり、そのまま真偽値として使用できると理解している場合には問題なかったのですが

> for(p = header; p; p=p->next)
> は、意味的には、「リストの要素が尽きるまで」ということを主張しているわけです。
> こういう風に、むしろ、「言いたいことを言う」コーディングでもあるわけです。

こちら場合には、NULLが真偽値として、そのまま使用できるのかが疑問としてあった為に質問させていただいた次第です。
NULLが真偽値の「偽」として評価されると理解できていれば、この使用方法も有りだと思いました。

ただ他の方のお礼にも書きましたとおり、この書き方では後日に自分で書いたソースをみて戸惑う可能性も充分にあると思いますので、自分で書く場合は明確に理解できる表現で書きたいと思います。

お礼日時:2014/10/01 07:45

回答としては、問題なく動作しますが、文法上やめるべきでしょう。



C言語では真偽値(true/false)がゼロとの比較となり、
かつNULLはゼロとなっているので、以下の書き方でどの様な処理系でも問題なく動作します。

for(p = header; p; p=p->next)

しかし、コーディングルール(プログラムを書くときのルール)としては、
判断の部分に値を入れるのはナンセンスですね。
つまり書く時のミスの原因や、後で他の人が見た時に意味が分かりづらいという事です。

if( 判断 ) { }
for( : 判断 : ) { }
while( 判断 ) { }

の様に判断を入れるところには == や > 、<というようにしっかりと
明示的に判断した結果を入れるべきです。

for(p = header; p!=NULL ; p=p->next)

以前は、メモリ容量の都合や、ルールが発展してなくて判断値と変数値を
ごちゃまぜにしたプログラムが存在しましたが、
これからは、他の人が見ることや、後で自分でわからなくならないように
ちゃんと動くだけではなくて、見やすい(可読性の高い)プログラムを作るように
するべきだと考えられています。
    • good
    • 0
この回答へのお礼

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

> しかし、コーディングルール(プログラムを書くときのルール)としては、
> 判断の部分に値を入れるのはナンセンスですね。
> つまり書く時のミスの原因や、後で他の人が見た時に意味が分かりづらいという事です。


仰るとおりだと思います。
自分でプログラムを書く場合にも見やすい書き方になるように留意したいと思います。

お礼日時:2014/09/30 20:47

バグで苦しみたくないなら、変数は使う前に初期化しておくべきでしょう。



プログラムの起動時、初期化せずに使えるかもしれませんが、
プログラムの仕様によっては、リストを再作成するなどの処理が発生した場合、
正常に動作しなくなりますよ。
    • good
    • 0
この回答へのお礼

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

お礼日時:2014/09/30 20:48

http://ja.wikipedia.org/wiki/Null#C.E8.A8.80.E8. …

難しく考えてはいけません。
リンク先にも書かれていますが「なにもない」という捉え方で十分です。

配列やグループの場合、これ以上何もないという意味でNULLを使用します。

0(ゼロ)で理解してしまうと、データとしてゼロ(数値のゼロではない)を使いたいときに混乱しますよね。データとしてのゼロとNULLのゼロは取り扱いが異なります。


>他の環境でもこの書き方で正常に動作するのでしょうか?
それは使用するコンパイラ次第。
よくわからないんだったら曖昧な書き方をするんじゃなくて、厳密な書き方を心がけるようにしましょう。
厳密なコーディングをしておけば、他人が見た時にナニをしたいかすぐに理解できます。が、曖昧な書き方だとコンパイラや環境の解釈の違いを調べなくてはいけませんからタイヘンです。
    • good
    • 0
この回答へのお礼

NULLをポインタの値と考えた場合には、たしかに「なにもない」という捉え方が適切だと思います。でも真偽値を書くべき場所に書かれた場合は、「なにもない」よりは0(番地)と捉えた方がわかり易いと私は思いました。

ご紹介いただきましたリンク先を拝見しますと、最近の環境では0または(void *)0 と定義させているということですので、質問の最初のforの書き方でも問題なさそうということですね。
ちなみに私の環境(MinGW gcc)でも後者の定義がなされていました。

とはいえ、わかり易い書き方をすべきということには同意致しますので、私も後者の様な書き方をしようと思います。

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

お礼日時:2014/09/30 20:15

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!