- ベストアンサー
NULLポインタが0でない処理系とは?
- C言語の規格上、NULLポインタは0とは限らない処理系も存在する。
- C99の仕様書ではNULLポインタを表す定数は0か(void*)0とされているが、これに反する処理系もある。
- 一部の処理系では、構造体のポインタメンバは明示的に初期化しないとNULLポインタにならない。
- みんなの回答 (10)
- 専門家の回答
質問者が選んだベストアンサー
日本語訳版 http://www.kouno.jp/home/c_faq/c5.html C FAQ、最近は読まれないのかなぁ
その他の回答 (9)
- wormhole
- ベストアンサー率28% (1626/5665)
>それにしても、80*86以外、寡聞にて聞かないのですが、(void*)0が全部のビット0となっていないアーキテクチャに向けて新しいプログラムを書く機会はそんなにあるんですか? おそらくないと思います。 でも絶対にないともいいきれないと思います。 >80*86もデータポインターと関数ポインターでサイズが違うと、void*型を引数に取る関数を使うときに困りそうなのですがどうしているのでしょうか? データポインタとコードポインタを同じに扱うことはほとんどないと思いますので データ用とコード用分けたりとかするんじゃないでしょうか。 >それとも、互換性のために考慮しておく必要があることの一つということでしょうか? 「互換性のため」という機会はあまりないと思いますが0とNULLを混同しないことは大事だと思いますよ。 長年プログラマやってる人でも char buf[100]; memset(buf, NULL, sizeof(buf)); や buf[0] = NULL; を平気で書く人いますし。
お礼
(void*)0と'\0'の内部表現が異なる環境の滅亡を祈ります。 > データポインタとコードポインタを同じに扱うことはほとんどないと思いますのでデータ用とコード用分けたりとかするんじゃないでしょうか。 こういうコードを書くのもどうかと思いますが、Cの規格としてはこういうコードを禁止していないと思いますので、コンパイラーを書く人も大変だなぁと。 (snip) void func(void *f) { void (*show)(int); show = f; show(10); } void show(int x) { printf("value: %d\n", x); } int main(void) { func(show); return EXIT_SUCCESS; } もちろん、現実にこれをやる場合は、pthread_createなどと同じくvoidへのポインター型の引数をとる関数を引数にした方がよいと思いますが。 > 0とNULLを混同しないことは大事だと思いますよ。 その点は同意します。強く型付けされた言語をしばらく使うと、違う型への代入が気持ち悪くて仕方ありません。ただ、voidへのポインターにはC99仕様書らしきものの6.3.2.3 Pointers / 1節で許容されており、FreeBSD coding styleで返値のvoid*はキャストするなと書いているので、自分としては例外ですが。 余談ですが、このコードはコンパイラーが警告を出しませんか? gccだと、c-typeck.cを通るときに警告が出ると思うのですが、ほかのコンパイラーではこういうチェックはしないのでしょうか。 さらに、余談ですが実用上問題ないですが、これのEXAMPLESを見ると修正したくなるかもしれません。 http://www.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3
補足
自分のお礼にある > もちろん、現実にこれをやる場合は、pthread_createなどと同じくvoidへのポインター型の引数をとる関数を引数にした方がよいと思いますが。 ですが、intを引数にとる関数へのポインターの方がいいですね。 pthread_createだと任意の処理をできるようにvoid *(*start_routine)(void *)をとりますが、先の例だと必ずint型の値を引数にとる関数とわかっていますから。
- wormhole
- ベストアンサー率28% (1626/5665)
>(void*)0もポインター型に代入される0もNULLポインターと同じ値ではあるけれど、それ以外の型の0が必ずしもNULLポインターと同じ値とは限らないと言うことですね。 そうではなくて、0をポインタにキャストするときにNULLポインタにコンパイラが読み替えてくれるということです。
お礼
視点の違いではないかと思います。 コンパイラから出力されるバイナリ、あるいは実行中の取り扱いについての説明だと、自分の書いた文でいいのではないかと思うのですが。 コンパイラの動きについての説明だと、コンパイラが抽象構文木を作るところで、宣言あるいは左辺値から型を決め、ポインター型だった場合はNULLポインターとして扱うのでしょうけれど。 実際の例としては、gccで-fdump-tree-allとかしてみたときに0となるか0Bとなるかでしょうか。
- wormhole
- ベストアンサー率28% (1626/5665)
C言語では整数値の内部表現は規定されてないですよね。 0が内部表現オールビット0以外の処理系があるかもしれない。 でも http://www.lysator.liu.se/c/c-faq/c-1.html の1.10の5に「ASCIIのNULL文字はオールビット0」、6に「NULL文字('\0')」と書いてあるんですよ。 ということは memset(sTest01,0,sizeof(sTest01)); と memset(sTest01,'\0',sizeof(sTest01)); は必ずしも同じではないのじゃないかと。 間違ってたらごめんなさい。
お礼
総合して考えると、下記の二つは必ずしも同じでは無いと思います。 memset(sTest01,0,sizeof(sTest01)); memset(sTest01,'\0',sizeof(sTest01)); しかし、(int)0から変換されてできた(unsigned char)0と'\0'が同じ内部表現でないアーキテクチャを作ると、glibcなど仕様通りの動きをしないライブラリーが出てくるのではないかと。
- Tacosan
- ベストアンサー率23% (3656/15482)
少なくとも C において memset(sTest01, 0, sizeof(sTest01)); と memset(sTest01, '\0', sizeof(sTest01)); には「見た目」以外に違いがないはずでは>#6.
お礼
http://www.freebsd.org/cgi/man.cgi?query=memset&sektion=3 void * memset(void *b, int c, size_t len); とint型をとった上で、 (converted to an unsigned char)らしいので、そうかもしれません。 C99の仕様らしいものの6.3.1.3 Signed and unsigned integersの1節にWhen a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchangedとあるので、int型の0をunsigned char型にcastした場合、unsigned char型の0になるはずで、これはlenが8以下の場合のglibcのbzeroと同じような動きになるはずです。
- wormhole
- ベストアンサー率28% (1626/5665)
自分で書いておいて何ですが・・・ >memset(sTest01, 0, sizeof(sTest01)); /* bzero(sTest01, sizeof(sTest01)); と同義 */ これ嘘ですね・・・ memset(sTest01, '\0', sizeof(sTest01)); /* bzero(sTest01, sizeof(sTest01)); と同義 */ こうだ・・・
お礼
たしかに、何気なく0と'\0'が同じだと思っていましたが、'\0'はすべてのbitが0と先のC99の仕様書らしいものの5.2.1 Characters / 3節で書いてありますね。そして、これまでの話を総合すると、0がかならずしも'\0'と考えない方が良さそうですね。 POSIX.1でもそんなことが書いてますね。 http://pubs.opengroup.org/onlinepubs/007904875/functions/bzero.html ちなみに、これはPOSIX.1-2004で、将来bzeroが取り下げられることが予告されており、POSIX.1-2008からは消えたようです。 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/strings.h.html 一方、広く使われてそうなlibcの実装であるglibcを見ると、 http://sourceware.org/git/?p=glibc.git;a=blob;f=string/bzero.c;h=b7a88ec9253bdf0c189c11c663301359ac6b777b;hb=HEAD では const op_t zero = 0;と((byte *) dstp)[0] = 0;となっていて、'\0'でなく、(op_t)0や(byte)0が使われているようで、これが'\0'と同じというのはどうやって保証しているのか疑問が残ります。一応、sysdeps/generic/memcopy.hに#define op_t unsigned long intとtypedef unsigned char byteがありますが。
- Tacosan
- ベストアンサー率23% (3656/15482)
ちろっと調べたら http://www.lysator.liu.se/c/c-faq/c-1.html が見付かった.
お礼
ありがとうございます。 1.14: Seriously, have any actual machines really used nonzero null pointers, or different representations for pointers to different types? ですね。 NULLポインターだからといって(レジスタに値として含まれている?)セグメントが0とは限らないということや、NULLポインターとして特殊な値を使っているということでしょうか。 それにしても、80*86以外、寡聞にて聞かないのですが、(void*)0が全部のビット0となっていないアーキテクチャに向けて新しいプログラムを書く機会はそんなにあるんですか? 80*86もデータポインターと関数ポインターでサイズが違うと、void*型を引数に取る関数を使うときに困りそうなのですがどうしているのでしょうか? それとも、互換性のために考慮しておく必要があることの一つということでしょうか?
- wormhole
- ベストアンサー率28% (1626/5665)
もう私が書くことはなさそうですが、 ソース上の整数値0が、NULLポインタを表すのは間違いないです。 ただしソース上の整数値0が、バイナリイメージ(メモリ上)で0とは限りません。 浮動小数点の規格であるIEEE754だとバイナリイメージで0は、数値+0なんですけどね。
お礼
ありがとうございます。 (void*)0もポインター型に代入される0もNULLポインターと同じ値ではあるけれど、それ以外の型の0が必ずしもNULLポインターと同じ値とは限らないと言うことですね。 符号ビットがあるからですよね。
- Tacosan
- ベストアンサー率23% (3656/15482)
(void *)0 がヌルポインタ定数であることは間違いありません. しかし, この規定はあくまで「ソースプログラム上そう書く」というだけであり, 「ビット表現がどうなっているか」については全く関与しません. 一般論として, 「異なる型にキャストした場合」にビット表現が保存されることは保証されていないですよね.
お礼
ありがとうございます。 マシンの内部表現とプログラムの字面は違うと言うことですね。 でも、「異なる型にキャストした場合」は残念なことしか起こらない予感しかしないです。
- notnot
- ベストアンサー率47% (4900/10358)
特殊なCPUでポインタのビット表現としてオールゼロはあり得ないようなケースでしょうか。 例えばレジスタは32bitだけどアドレス空間は24bitで上位8ビットを別の目的で使っているとか。 なお、NULLポインタは内部表現がどうであれ、数値0と比較すると常に等しいし、ポインタに数値0を(キャストして)代入するとそのアーキテクチャのNULLポインタのビットパターンに変換して代入してくれるはずなので、#define NULL 0 はどんな場合にでも使えるはずです。
お礼
ありがとうございます。 思わずLISP処理系の実装を連想してしまいました。(あちらは下位bitを別の目的に転用するという話ですが) プログラムの字面で0と書いてあるものが、必ずしもマシンの内部でbitがすべて0になった値となっているとは限らないということですね。ポインター型の値との演算を行っていることを検出してコンパイラーが勝手にマシン内でNULLポインターを表す値に変えたマシン語を出すということですね。 int型のポインター型にキャストされている値を+1すると、コンパイル後のマシン語では1でなく、sizeof(int)足されているようなものでしょうか。
お礼
ありがとうございます。 C FAQ 自分は初耳でした。
補足
ベストアンサーが一番上に表示されると思うので、ここにまとめを書きます。 Q1: どの処理系だとNULLポインタが0ではないでしょうか? 質問文の0をすべてのbitが0の値とすると、Honeywell-Bullのメインフレーム、Cyber 180シリーズなどが該当します。 (C FAQより) Q2: bzero(&baz, sizeof(baz));すればbaz.barはNULLポインター定数に初期化されますか? すべてのbitが0の値に初期化されるので、Q1の返答で書いたような処理系の場合にはNULLポインター定数と異なる値(すべてのビットが0の値)に初期化されます。 たとえば、06000がNULLポインター値として使われるHoneywell-Bullのメインフレームでは、bzeroされたポインター変数の値はNULLポインターの値になっていません。 Q3: NULLポインターがすべてのビットが0と同値でないアーキテクチャに向けて新しいプログラムを書く機会はありますか? おそらく無いでしょう。しかし、絶対にないとは言えません。 いずれにせよ、0とNULLを混同しないことは大事だと思います。 本当はお三方全員をベストアンサーとしたいところではありますが、これから先に同じ疑問を持った人にも一番役に立つであろう回答はやはりC FAQの日本語版だと思いますので、wormholeさんをベストアンサーにしたいと思います。 C FAQ自体はTacosanさんの示しているところのほかに次のURLでも原典と思われるものを閲覧できるようです。 http://www.faqs.org/faqs/C-faq/faq/ http://c-faq.com/ しかしながら、C言語に関する資料としては、Tacosanさんの紹介されていたところがよくまとまっているように思います。 http://www.lysator.liu.se/c/ ここからたどって見つけましたが、ISO/IEC 9899:2011の最終原稿もここから入手できますね。 http://www.open-std.org/jtc1/sc22/wg14/www/projects#9899