趣味でプログラムを勉強しております。
とある書籍にてリスト構造を辿るソースが以下の様になっていました。
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(番地)とは限らない」ということになっています。
※処理系(コンパイラなど)によりきまります。
ご回答ありがとうございました。
ご紹介いただいたリンク先の内容は今ひとつ理解できなかったのですが
>・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」と等しいと評価はされませんでした。
これは私の考えが安易すぎたせい?それもと処理系依存のせいなのでしょうか?
No.9
- 回答日時:
そもそも、「ある特定の番地」をポインタにセットする、「標準的な」方法はありません。
規格上では、ポインタはポインタであって、必ずしも整数型による、番地表現と互換性があるわけではないので。
で、「規格」と「実装」という区別になるのですが、
・ぶっちゃけ、普通に使われるマイコンは、ポインタに、整数定数0を代入すると、0番地を意味するようになるケースが、かなり多いです。
※組み込みの場合、アセンブラレベルで確認するでしょうし。
・実は、「整数定数0」と回りくどい言い方をしていますが、int 型の 変数に代入された値 0 は、「整数定数」ではありません。
なので、
int adr = 0;
char *p;
p = adr;
で、p がゼロ番地をポイントする可能性は高いです。
※ただし、上述したように、整数型をポインタに代入することの結果は、一般的には決定されません。
こんなところでしょうか。
> そもそも、「ある特定の番地」をポインタにセットする、「標準的な」方法はありません。
> 規格上では、ポインタはポインタであって、必ずしも整数型による、番地表現と互換性があるわけではないので。
ポインタについては、アセンブラの間接アドレッシングの様なものを漠然とイメージしていたのですが、そう単純ではないのですね。
了解しました。
何度もお時間を割いていただきまして本当にありがとうございました。
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 に、このあたりの説明があります。
このページをまず、勉強してください。
この回答への補足
申し訳ありません、お礼をした後で疑問が生じました。
ポインタ変数に物理的な0番地を代入する場合はどの様な記述になるのでしょうか?
組み込み系のC言語では存在するような気がします。。。
何度もご回答ありがとうございます。
#defineによる定義は単に文字列に定数を割り当てたに過ぎないというのはわかりました。
ヌルポインタについて、頭が混乱してきたので整理してみました。
1. 「何処も指していないヌルポインタ」というものが、抽象表現(或いはコンパイラの内部表現)として存在する。
これはどの様に内部表現されているかはプログラマには関知できない(する必要がない)
2. 「何処も指していないヌルポインタ」を見える形に表現しようとすると数値0に変換される。
3. ポインタ変数に数値0を代入すると、内部で「何処も指していないヌルポインタ」に変換される。
4. なので、真偽判定する場所に「何処も指していないヌルポインタ」が代入されているポインタ変数を記述すると、式(ポインタ変数)の評価結果として数値0が返るために「偽」判定となる。
上記は(少なくとも最近のC言語では)保証されている。
という理解でよろしいですか?
No.6
- 回答日時:
>確認の為
>試しに私の環境(MinGW gcc)でstdio.hのNULLの定義を
>
>#define NULL ((void *)5)
>
>と書き換えた上で、以下のソースを実行してみました。
何を確認しようとされたのでしょうか。
ヘッダファイル内のマクロ定義を書き換えたからといって、その処理系(コンパイラなど)が内部に保持しているNULLの定義が書き換わるわけじゃないですけど。
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では、こういう使い方を意図している場面もあって、こういう表現は、割と、一般的です。
(少なくとも、一字一句プログラムを解釈する場面を超えれば)
再びのご回答ありがとうございました。
> 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)
以前は、メモリ容量の都合や、ルールが発展してなくて判断値と変数値を
ごちゃまぜにしたプログラムが存在しましたが、
これからは、他の人が見ることや、後で自分でわからなくならないように
ちゃんと動くだけではなくて、見やすい(可読性の高い)プログラムを作るように
するべきだと考えられています。
ご回答ありがとうございました。
> しかし、コーディングルール(プログラムを書くときのルール)としては、
> 判断の部分に値を入れるのはナンセンスですね。
> つまり書く時のミスの原因や、後で他の人が見た時に意味が分かりづらいという事です。
仰るとおりだと思います。
自分でプログラムを書く場合にも見やすい書き方になるように留意したいと思います。
No.1
- 回答日時:
難しく考えてはいけません。
リンク先にも書かれていますが「なにもない」という捉え方で十分です。
配列やグループの場合、これ以上何もないという意味でNULLを使用します。
0(ゼロ)で理解してしまうと、データとしてゼロ(数値のゼロではない)を使いたいときに混乱しますよね。データとしてのゼロとNULLのゼロは取り扱いが異なります。
>他の環境でもこの書き方で正常に動作するのでしょうか?
それは使用するコンパイラ次第。
よくわからないんだったら曖昧な書き方をするんじゃなくて、厳密な書き方を心がけるようにしましょう。
厳密なコーディングをしておけば、他人が見た時にナニをしたいかすぐに理解できます。が、曖昧な書き方だとコンパイラや環境の解釈の違いを調べなくてはいけませんからタイヘンです。
NULLをポインタの値と考えた場合には、たしかに「なにもない」という捉え方が適切だと思います。でも真偽値を書くべき場所に書かれた場合は、「なにもない」よりは0(番地)と捉えた方がわかり易いと私は思いました。
ご紹介いただきましたリンク先を拝見しますと、最近の環境では0または(void *)0 と定義させているということですので、質問の最初のforの書き方でも問題なさそうということですね。
ちなみに私の環境(MinGW gcc)でも後者の定義がなされていました。
とはいえ、わかり易い書き方をすべきということには同意致しますので、私も後者の様な書き方をしようと思います。
ご回答ありがとうございました。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- PHP PHPでCSVを出力するさいに、ループの中で前の行の値を変更したい 3 2022/10/27 17:44
- AJAX JavascriptからPHPへのAjax通信でnullが返ってくる 3 2022/08/03 22:00
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- PostgreSQL DBFluteについて質問です。 環境:PostgreSQL java8 前提:webアプリケーショ 1 2022/07/07 00:49
- C言語・C++・C# leetcode21 1 2022/04/21 11:53
- その他(プログラミング・Web制作) pythonのmap、結果の利用は1度だけ? 5 2022/06/11 12:33
- その他(プログラミング・Web制作) python 3.10で 同じlistに同じ構文で同じデータ代入した結果が異なる現象発生 7 2022/06/18 11:08
- PHP 配列の値の更新方法について 1 2022/08/05 09:49
関連するカテゴリからQ&Aを探す
おすすめ情報
- ・「みんな教えて! 選手権!!」開催のお知らせ
- ・漫画をレンタルでお得に読める!
- ・「黒歴史」教えて下さい
- ・2024年においていきたいもの
- ・我が家のお雑煮スタイル、教えて下さい
- ・店員も客も斜め上を行くデパートの福袋
- ・食べられるかと思ったけど…ダメでした
- ・【大喜利】【投稿~12/28】こんなおせち料理は嫌だ
- ・前回の年越しの瞬間、何してた?
- ・【お題】マッチョ習字
- ・モテ期を経験した方いらっしゃいますか?
- ・一番最初にネットにつないだのはいつ?
- ・好きな人を振り向かせるためにしたこと
- ・【選手権お題その2】この漫画の2コマ目を考えてください
- ・2024年に成し遂げたこと
- ・3分あったら何をしますか?
- ・何歳が一番楽しかった?
- ・治せない「クセ」を教えてください
- ・【大喜利】【投稿~12/17】 ありそうだけど絶対に無いことわざ
- ・【選手権お題その1】これってもしかして自分だけかもしれないな…と思うあるあるを教えてください
- ・集合写真、どこに映る?
- ・自分の通っていた小学校のあるある
- ・フォントについて教えてください!
- ・これが怖いの自分だけ?というものありますか?
- ・スマホに会話を聞かれているな!?と思ったことありますか?
- ・それもChatGPT!?と驚いた使用方法を教えてください
- ・見学に行くとしたら【天国】と【地獄】どっち?
- ・これまでで一番「情けなかったとき」はいつですか?
- ・この人頭いいなと思ったエピソード
- ・あなたの「必」の書き順を教えてください
- ・10代と話して驚いたこと
- ・14歳の自分に衝撃の事実を告げてください
- ・人生最悪の忘れ物
- ・あなたの習慣について教えてください!!
- ・都道府県穴埋めゲーム
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
セグメントエラー
-
fopne で失敗する原因
-
ExcelVBAでのkernel32(64bit)
-
C言語のポインタに直接アドレス...
-
Run-Time Check Failure #3とい...
-
ハンドルはポインタか
-
init関数の意味
-
参照型で受け取った引数をポイ...
-
TCHAR文字列内の検索について
-
C言語で構造体の参照渡しができ...
-
アドレス値はどの型にキャスト...
-
戻り値で構造体を返すことは可...
-
ReadFileの読み込みエラーについて
-
アプリを32bitから64bit移行
-
CImage GetBitsメソッドについて
-
自作DLLの引数について、ポイン...
-
パスからファイル名を抽出
-
C++で関数ポインタから関数名を...
-
単方向リスト
-
関数ポインタの利点
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
セグメントエラー
-
C言語のポインタに直接アドレス...
-
init関数の意味
-
Run-Time Check Failure #3とい...
-
戻り値で構造体を返すことは可...
-
fopne で失敗する原因
-
ExcelVBAでのkernel32(64bit)
-
C言語でのconstを返す関数
-
LPSTR型の初期化について
-
参照型で受け取った引数をポイ...
-
ハンドルはポインタか
-
ハンドル、アドレス、ポインタ...
-
CImage GetBitsメソッドについて
-
C++で関数ポインタから関数名を...
-
Cで作成したDLL関数をVBから呼...
-
アプリを32bitから64bit移行
-
コンストラクタでnewを失敗した...
-
TCHAR文字列内の検索について
-
c言語で任意のファイルから読み...
-
デバイスハンドルとは?
おすすめ情報