C言語関係掲示板

過去ログ

No.1148 NULLのキャスト2

[戻る] [ホームページ]
No.14632

お久しぶりです。
投稿者---Duke(2004/06/15 17:16:40)


ご無沙汰してました。Dukeです。
NULLのキャストについていろいろとお話させていただきましたが、
リアル多忙で尻切れになってしまいましたので、再度掲載させて
いただきます。

char *p
char **p
char ***p
と三つほど変数を用意して、各変数にNULLをキャストして代入してみてください。
特にchar ***については
pと*pと**pに代入を書かせてみるといいかも知れませんね。

わかっている人には冗長、でもポインタ変数を勉強してすぐの人には
それが何を指し、どんなデータ型なのかを理解できていない場合にが
多いですね。その演習のためにも有効な一つの方法論として考えて
みてください。

ついでに構造体を指すポインタや二次元配列を管理するポインタを指すポインタ、
関数ポインタ配列を指すポインタなどでこれをやせてみると
非常に面白いキャストを書いて迷っている人もいますからね〜

まぁ代入するものがNULLである必要があるかどうかはありますけどね。
getsの戻り値やfopenの戻り値などもわざとキャストしたNULLと判断
させると、勘違いさんには警告を出してくれますから・・・
などいう理由もありでしょうね。

あえて書けといわれて書けるか?
それがポイントと言えませんか。





No.14640

Re:お久しぶりです。
投稿者---ニタチ(2004/06/15 19:46:16)


 自分で理解していないと思ったので、挑戦してみました。

>char *p
>char **p
>char ***p
>と三つほど変数を用意して、各変数にNULLをキャストして代入してみてください。

 char *p = (char*)NULL;
 char **p = (char**)NULL;
 char ***p = (char***)NULL;
 ということですか???


>特にchar ***については
>pと*pと**pに代入を書かせてみるといいかも知れませんね。

 char p;
 char *p1;
 char **p2;
 char ***p3;

 p2 = *p3;
 p1 = **p3;
 p = ***p3;

>わかっている人には冗長、でもポインタ変数を勉強してすぐの人には
>それが何を指し、どんなデータ型なのかを理解できていない場合にが
>多いですね。

 char *pは、アドレスを値として持つポインタ変数。
 char **pは、ポインタ変数のアドレスを値として持つポインタ変数。
 char ***pは、char**のポインタ変数のアドレスを値として持つポインタ変数。

 と、以上のようになりました。
 Dukeさん、ご指摘よろしくお願いします。



No.14650

Re:お久しぶりです。
投稿者---YuO(2004/06/15 21:05:18)


とりあえず書いておくと,

> char *pは、アドレスを値として持つポインタ変数。
> char **pは、ポインタ変数のアドレスを値として持つポインタ変数。
> char ***pは、char**のポインタ変数のアドレスを値として持つポインタ変数。

  • char c;と宣言したときのcは「char」型を持つ識別子
  • char *pc;と宣言したときのpcは「char型へのポインタ」型を持つ識別子
  • char **ppc;と宣言したときのppcは「『char型へのポインタ』型へのポインタ」型を持つ識別子
  • char ***pppc;と宣言したときのpppcは「『「char型へのポインタ」型へのポインタ』型へのポインタ」型を持つ識別子
です。

で,「T型へのポインタ」というのは,T型のオブジェクトを参照するための値であり,
それを保持する型が「T型へのポインタ型」です。


ちなみに,
>>ついでに構造体を指すポインタや二次元配列を管理するポインタを指すポインタ、
>>関数ポインタ配列を指すポインタなどでこれをやせてみると
は,上記の「ポインタ」を全て「ポインタ型」と置き換えて読んだ場合,それぞれの型は
構造体型へのポインタ型
  • 単純に,構造体の型がTであれば,T *型。
二次元配列へのポインタ型へのポインタ型
  • ポインタ型なので,「二次元配列へのポインタ型」をT1とおくと,T1 (*)型。
  • 「二次元配列」をT2とおくと,それへのポインタ型はT2 (*)だから,T2 (*(*))型。
  • 「二次元配列」は要素をT型とするとT ([][])型。よって,T ((*(*))[][])型。
  • 冗長な括弧を取り去って,T (**)[][]型。
関数へのポインタ型の配列型へのポインタ型
  • ポインタ型なので,「関数へのポインタ型の配列型」をT1とおくと,T1 (*)型。
  • 「関数へのポインタ型」をT2とおくと,その配列型はT2 ([])だから,T2 ((*)[])型。
  • 「関数の型」をT3とおくと,それへのポインタ型はT3 (*)だから,T3 (*((*)[]))型。
  • 「関数の型」は戻り値の型をT,引数をAとすると,T ()(A)だから,T ((*((*)[])))(A)型。
  • 冗長な括弧を取り去って,T (*(*)[])(A)型。
となります。


No.14665

Re:お久しぶりです。
投稿者---ニタチ(2004/06/16 13:19:34)


構造体型へのポインタ型
  • 単純に,構造体の型がTであれば,T *型。
二次元配列へのポインタ型へのポインタ型
  • ポインタ型なので,「二次元配列へのポインタ型」をT1とおくと,T1 (*)型。
  • 「二次元配列」をT2とおくと,それへのポインタ型はT2 (*)だから,T2 (*(*))型。
  • 「二次元配列」は要素をT型とするとT ([][])型。よって,T ((*(*))[][])型。
  • 冗長な括弧を取り去って,T (**)[][]型。
関数へのポインタ型の配列型へのポインタ型
  • ポインタ型なので,「関数へのポインタ型の配列型」をT1とおくと,T1 (*)型。
  • 「関数へのポインタ型」をT2とおくと,その配列型はT2 ([])だから,T2 ((*)[])型。
  • 「関数の型」をT3とおくと,それへのポインタ型はT3 (*)だから,T3 (*((*)[]))型。
  • 「関数の型」は戻り値の型をT,引数をAとすると,T ()(A)だから,T ((*((*)[])))(A)型。
  • 冗長な括弧を取り去って,T (*(*)[])(A)型。
となります。

 う〜ん難しいですね・・・。
 実務ではこのような型を駆使するのでしょうか?


No.14642

Re:お久しぶりです。
投稿者---かずま(2004/06/15 20:01:56)


「お久しぶり」という内容について議論したいのですか?
適切な題名が書けないのは、題名の意味が分からないということですか?


> 特にchar ***については
> pと*pと**pに代入を書かせてみるといいかも知れませんね。

何が言いたいのかさっぱり分からないので、具体的な例を書いて説明してみて
ください。


> わかっている人には冗長、でもポインタ変数を勉強してすぐの人には
> それが何を指し、どんなデータ型なのかを理解できていない場合にが
> 多いですね。その演習のためにも有効な一つの方法論として考えて
> みてください。

ポインタの理解と、キャストは無関係です。

間違ったポインタ同士の比較や代入を書くと、コンパイラは警告を出してくれ
ますが、そんなポインタでもキャストを使って同じポインタに見せかけると
コンパイラは警告を出しません。キャストは有害です。


> getsの戻り値やfopenの戻り値などもわざとキャストしたNULLと判断
> させると、勘違いさんには警告を出してくれますから・・・

if (gets(buf) == NULL) と書いても、
if (gets(buf) == (char *)NULL)と書いても、
コンパイラは警告を出しません。

if (gets(buf) == (int *)NULL)と書いて、警告を出させろということですか?




No.14644

Re:お久しぶりです。
投稿者---YuO(2004/06/15 20:06:36)


>NULLのキャストについていろいろとお話させていただきましたが、
>リアル多忙で尻切れになってしまいましたので、再度掲載させて
>いただきます。

もとは,過去ログ1018 : NULLのキャストですね。
ちゃんと元記事へのリンクを貼ってください。


>char *p
>char **p
>char ***p
>と三つほど変数を用意して、各変数にNULLをキャストして代入してみてください。


それぞれ,
p = (char *)0;
p = (char **)0;
p = (char ***)0;
で済む話ですが。
そもそも,私は空ポインタ定数をキャストするのはナンセンスだと言っていますが。


>わかっている人には冗長、でもポインタ変数を勉強してすぐの人には
>それが何を指し、どんなデータ型なのかを理解できていない場合にが
>多いですね。その演習のためにも有効な一つの方法論として考えて
>みてください。

方法論として間違っていると思いますが。
型が正しく理解できているのであればキャストを書く必要がないのですから。

本来互換性のない型であっても,キャストはできてしまいます。
そのため,代入する型とされる型さえ合わせてしまえば,
型が理解できなくてもキャストで誤魔化せてしまえます。
void func (void);

/* ... */
int * p = (int *)func;
とやっても,キャストによって誤魔化しが利いてコンパイルできてしまいます。
キャストを書いて型を理解させようということ自体が方法論として間違っているのであり,
キャストで誤魔化す人間を量産するだけの結果になりますよ。


>あえて書けといわれて書けるか?
>それがポイントと言えませんか。

だから言えないと。
そもそも何故キャストなのですか?



No.14645

Re:お久しぶりです。
投稿者---円零(2004/06/15 20:21:29)


そもそも、NULLポインタに型はあるんですか?


No.14648

Re:お久しぶりです。
投稿者---円零(2004/06/15 20:35:14)


すみません、おかしな事を書きました。
NULLポインタ、じゃなくてNULLポインタの値「NULL」には型はないんじゃないかと思ったんです。


No.14649

Re:お久しぶりです。
投稿者---YuO(2004/06/15 20:43:15)


>>そもそも、NULLポインタに型はあるんですか?
>NULLポインタ、じゃなくてNULLポインタの値「NULL」には型はないんじゃないかと思ったんです。

実質的に無いと考えて良いでしょう。


まず,マクロNULLは
>処理系定義の空ポインタ定数に展開する。
とされています。

次に,空ポインタ定数に一応型はあります。
>値0をもつ整数定数式又はその定数式をvoid *にキャストした式を,空ポインタ定数と呼ぶ。
との記述があるので,整数定数式の型又はvoid *型になります。

しかし,本来void *型は関数へのポインタ型に代入することはできませんし,
整数型はそもそもポインタ型に代入することはできません。

つまり,空ポインタ定数は特殊な扱いをされているので,
特定の型であるとしても,その型の取り扱いからは外れることになります。
これが「実質的に無い」とする根拠です。


ただし,実際には整数定数式の型若しくはvoid *型になりますから,
可変個引数をとる関数に渡す場合には,void *にキャストする必要があります。



No.14646

Re:お久しぶりです。
投稿者---shu(2004/06/15 20:34:11)


型が知りたかったら、変数にしろ関数にしろ宣言部分を見ればいい。

NULLを使用するたびに、いちいち型はなんだったかなぁって、
確認するようなこと、面倒でやってられないと、私は思います。


No.14655

正しい解釈が帰ってくるっていいなぁ
投稿者---Duke(2004/06/15 23:16:59)


正しい解釈が帰ってくるっていいなぁ

まぁついでに読んでみてくださいな。

例えば
char a[100][100];
がありました。
その人はこの二次元配列を管理するために
(ソートでもしたかったんでしょうね)
char *pd[100];
を用意したわけです。
さらにpdを関数にでも渡したんでしょうね。
char **pa;
を活用しようと考えたわけです。
その人はどうしてもpdの終端にNULLを入れたかった。
しかし書いたコードが

pa = NULL;

おひおひ、っておもいますよね?
しっかりメモリの図を書きましょう。
paの宣言読みなおしましょ。
っていいたいですよね。でも本人はいたって真剣。

「ほんとはこういう使い方しないけどね、NULLでも自分の
考えている型にキャストしてごらんなさいな、なんか警告でない?」
「警告でるね〜どうしてその型にキャストしたのかな?」

なんてことをやるとその人が一体どんな型に勘違いしている
のか解るわけですよ。

理詰めでどんなに考えさせてもど〜しても理解できない人には
有効なもんですよ。
大切なのは「ホントはこうしないんだよ」っていう一言がポイント
なんですけどね

もちろん
getsの戻りを(int *)NULLで判断したら警告でますよ。
でもこれを本気で書く人もいるんですよ〜
「はい、君は関数の仕様書読み直してね」
っていえますよ。

世の中には
char ***p
になんで
p = NULL
*p = NULL
**p = NULL
が書けるの〜?悩んでいる人がいっぱいいるってことですな。



No.14656

Re:正しい解釈が帰ってくるっていいなぁ
投稿者---RiSK(2004/06/15 23:59:33)


> 「お久しぶり」という内容について議論したいのですか?
> 適切な題名が書けないのは、題名の意味が分からないということですか?

読んだ?


No.14657

Re:正しい解釈が帰ってくるっていいなぁ
投稿者---ぽこ(2004/06/16 02:24:43)


前スレから読ませていただきましたが、NULLを明示的にキャストする
有り難味がいまいち理解できません。

Dukeさんが仰っている利点というものは、以下の解釈で合ってるでしょうか?

・"暗黙の型変換"という邪道な手法に染まらずに済む。
・初学者の型認識トレーニングになる。
・ソースコードのドキュメント性が向上する。



No.14659

Re:正しい解釈が帰ってくるっていいなぁ
投稿者---円零(2004/06/16 09:28:32)


NULL文字とNULLポインタを混同したと言う話ですか。
でもNULL文字の時は'\0'を使えば済む話だったりしませんか。



No.14663

Re:正しい解釈が帰ってくるっていいなぁ
投稿者---ニタチ(2004/06/16 12:45:40)


>世の中には
>char ***p
>になんで
>p = NULL
>*p = NULL
>**p = NULL
>が書けるの〜?悩んでいる人がいっぱいいるってことですな。

 それはポインタの仕組みをちゃんと理解すれば、自然とわかるようになるのではないでしょうか?

>あえて書けといわれて書けるか?
>それがポイントと言えませんか。
 
 あの〜〜、私は書けたのでしょうか?


No.14666

Re:正しい解釈が帰ってくるっていいなぁ
投稿者---俳人(2004/06/16 18:31:24)


空気が悪くなってきたのでここで一句

転がった
  うんこを食べる
     おじいちゃん

評:”うんこを食べる”と言う季語が斬新ですね







No.14689

ヌルポインタの誤解例
投稿者---とおり(2004/06/17 13:20:35)


自称中級者です。
最近、"C言語によるプログラミング[スーパーリファレンス編]"という書籍を買ったのですが、
それを読んで、今までヌルポインタを誤って理解していたことに気づきました。
他にも私のような人がいそうなので書きこみます。
まだ理解したてなので誤りがあれば指摘お願いします。

C言語で NULL は (void *)0 と定義されていることが多いと思います。
このことから、「アドレスゼロ番地がヌルポインタ」だと理解していました。
たぶんゼロ番地は誰も使っちゃいけないところなんだろう、という感じでした。

だから、C++言語での NULL は 0 と定義されているのを知って、
なんで分かり難くしたんだろう、C言語の定義の方が綺麗だよなぁ、などと思っていました。

しかしこの理解は誤りで、実際は、
「C/C++言語ではプログラムソース上でヌルポインタそのものを表すことはできない。
 その代替として、ポインタと関係する箇所の 0 がコンパイル時にヌルポインタに変換される」
だと理解し直しました。
"ポインタと関係する箇所"とは ptr=0 や ptr!=0 等のことで、これがコンパイル時に、
ptr=ヌルポインタ と ptr!=ヌルポインタ に変換されるようです。

このことを知って、今はC++言語のNULL定義の方が自然に感じるようになりました。
逆にC言語のNULL定義は誤解を生みやすい不自然な定義に感じます。
マクロとしてではなく、ヌルポインタそのものを表すキーワードがC/C++言語に用意されていれば、
ヌルポインタはもっと理解し易いものになってただろうに、などと思いました。



No.14706

Re:ヌルポインタの誤解例
投稿者---あかま(2004/06/17 17:21:15)


>このことから、「アドレスゼロ番地がヌルポインタ」だと理解していました。
>たぶんゼロ番地は誰も使っちゃいけないところなんだろう、という感じでした。

ワタシも最近まで思ってましたね(笑)

>C言語で NULL は (void *)0 と定義されていることが多いと思います。
>だから、C++言語での NULL は 0 と定義されているのを知って、
>なんで分かり難くしたんだろう、C言語の定義の方が綺麗だよなぁ、などと思っていました。

>このことを知って、今はC++言語のNULL定義の方が自然に感じるようになりました。
>逆にC言語のNULL定義は誤解を生みやすい不自然な定義に感じます。

個人的には(void *)0の方が

int i;
if(i == NULL){}

なんてことを書いたときにコンパイラが警告を出してくれるので便利だと思うのですが。
あくまで"ポインタ"として扱うのが明示的で分かりやすいと思います。

>マクロとしてではなく、ヌルポインタそのものを表すキーワードがC/C++言語に用意されていれば、
>ヌルポインタはもっと理解し易いものになってただろうに、などと思いました。
言語処理系は詳しくないですが、
ヌルポインタは比較や演算に用いるのでなんらかの数値じゃないとまずいのではないでしょうか。
したがって、マクロでもキーワードでも結局は数値に変換する必要があると思うのですが、
「int」や「char」などと同じキーワード(予約語という意味ですよね?)が数値に変換されるのは逆に違和感を憶えます。
「キーワードを変数に代入」なんてのも変ですし。
あくまで数値をわかりやすく皮を被せるという意味で、マクロは最適だと思います。