プロが教える店舗&オフィスのセキュリティ対策術

http://oshiete.goo.ne.jp/qa/7458540.html にて、
> C言語の規格上、0はNULLポインタとして扱われますが
> NULLポインタは0とは限らないので(0でない処理系もあります)。
と他の回答者が答えていたのが妙に引っかかりました。

後学のために知っておきたいのですが、
どの処理系だとNULLポインタが0ではないのかどなたかご存じないですか?


気になって、C99の仕様書らしいものを検索で見つけたのですが、
http://www.open-std.org/jtc1/sc22/wg14/www/docs/ …
これによると、6.3.2.3 Pointers の 3節 にて、
> An integer constant expression with the value 0,
> or such an expression cast to type void *, is called a null pointer constant.
とあり、7.17 Common definitions <stddef.h> の 3節 でも、
> The macros are
> NULL
> which expands to an implementation-defined null pointer constant; (略)
とあるので、NULLポインタを表す定数は0か(void*)0だと思っていたのですが。

言い換えると、
struct foo {
char* bar;
};
と構造体が定義されている時、
struct foo baz;
bzero(&baz, sizeof(baz));
すればbaz.barはNULLポインタ定数に初期化されると思っていたのですが、
これだとダメな処理系はどういう処理系で、どんな用途で使うのでしょうか?
おそらく、0でない値にした目的や歴史的経緯があるのではないかと推測するのですが。

A 回答 (10件)

日本語訳版


http://www.kouno.jp/home/c_faq/c5.html

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/proje …

補足日時:2012/05/13 10:40
    • good
    • 0
この回答へのお礼

ありがとうございます。

C FAQ 自分は初耳でした。

お礼日時:2012/05/12 09:03

>それにしても、80*86以外、寡聞にて聞かないのですが、(void*)0が全部のビット0となっていないアーキテクチャに向けて新しいプログラムを書く機会はそんなにあるんですか?



おそらくないと思います。
でも絶対にないともいいきれないと思います。

>80*86もデータポインターと関数ポインターでサイズが違うと、void*型を引数に取る関数を使うときに困りそうなのですがどうしているのでしょうか?

データポインタとコードポインタを同じに扱うことはほとんどないと思いますので
データ用とコード用分けたりとかするんじゃないでしょうか。

>それとも、互換性のために考慮しておく必要があることの一つということでしょうか?

「互換性のため」という機会はあまりないと思いますが0とNULLを混同しないことは大事だと思いますよ。
長年プログラマやってる人でも
char buf[100];
memset(buf, NULL, sizeof(buf));

buf[0] = NULL;
を平気で書く人いますし。

この回答への補足

自分のお礼にある
> もちろん、現実にこれをやる場合は、pthread_createなどと同じくvoidへのポインター型の引数をとる関数を引数にした方がよいと思いますが。

ですが、intを引数にとる関数へのポインターの方がいいですね。
pthread_createだと任意の処理をできるようにvoid *(*start_routine)(void *)をとりますが、先の例だと必ずint型の値を引数にとる関数とわかっていますから。

補足日時:2012/05/13 10:29
    • good
    • 0
この回答へのお礼

(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=getaddr …

お礼日時:2012/05/12 13:34

>(void*)0もポインター型に代入される0もNULLポインターと同じ値ではあるけれど、それ以外の型の0が必ずしもNULLポインターと同じ値とは限らないと言うことですね。



そうではなくて、0をポインタにキャストするときにNULLポインタにコンパイラが読み替えてくれるということです。
    • good
    • 0
この回答へのお礼

視点の違いではないかと思います。

コンパイラから出力されるバイナリ、あるいは実行中の取り扱いについての説明だと、自分の書いた文でいいのではないかと思うのですが。

コンパイラの動きについての説明だと、コンパイラが抽象構文木を作るところで、宣言あるいは左辺値から型を決め、ポインター型だった場合はNULLポインターとして扱うのでしょうけれど。

実際の例としては、gccで-fdump-tree-allとかしてみたときに0となるか0Bとなるかでしょうか。

お礼日時:2012/05/12 12:59

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));
は必ずしも同じではないのじゃないかと。

間違ってたらごめんなさい。
    • good
    • 0
この回答へのお礼

総合して考えると、下記の二つは必ずしも同じでは無いと思います。
memset(sTest01,0,sizeof(sTest01));
memset(sTest01,'\0',sizeof(sTest01));

しかし、(int)0から変換されてできた(unsigned char)0と'\0'が同じ内部表現でないアーキテクチャを作ると、glibcなど仕様通りの動きをしないライブラリーが出てくるのではないかと。

お礼日時:2012/05/12 10:37

少なくとも C において


memset(sTest01, 0, sizeof(sTest01));

memset(sTest01, '\0', sizeof(sTest01));
には「見た目」以外に違いがないはずでは>#6.
    • good
    • 0
この回答へのお礼

http://www.freebsd.org/cgi/man.cgi?query=memset& …
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と同じような動きになるはずです。

お礼日時:2012/05/12 10:25

自分で書いておいて何ですが・・・



>memset(sTest01, 0, sizeof(sTest01)); /* bzero(sTest01, sizeof(sTest01)); と同義 */

これ嘘ですね・・・

memset(sTest01, '\0', sizeof(sTest01)); /* bzero(sTest01, sizeof(sTest01)); と同義 */

こうだ・・・
    • good
    • 0
この回答へのお礼

たしかに、何気なく0と'\0'が同じだと思っていましたが、'\0'はすべてのbitが0と先のC99の仕様書らしいものの5.2.1 Characters / 3節で書いてありますね。そして、これまでの話を総合すると、0がかならずしも'\0'と考えない方が良さそうですね。

POSIX.1でもそんなことが書いてますね。
http://pubs.opengroup.org/onlinepubs/007904875/f …
ちなみに、これはPOSIX.1-2004で、将来bzeroが取り下げられることが予告されており、POSIX.1-2008からは消えたようです。
http://pubs.opengroup.org/onlinepubs/9699919799/ …

一方、広く使われてそうなlibcの実装であるglibcを見ると、 http://sourceware.org/git/?p=glibc.git;a=blob;f= … では 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がありますが。

お礼日時:2012/05/12 09:54

ちろっと調べたら


http://www.lysator.liu.se/c/c-faq/c-1.html
が見付かった.
    • good
    • 0
この回答へのお礼

ありがとうございます。

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*型を引数に取る関数を使うときに困りそうなのですがどうしているのでしょうか?
それとも、互換性のために考慮しておく必要があることの一つということでしょうか?

お礼日時:2012/05/12 08:44

もう私が書くことはなさそうですが、


ソース上の整数値0が、NULLポインタを表すのは間違いないです。
ただしソース上の整数値0が、バイナリイメージ(メモリ上)で0とは限りません。

浮動小数点の規格であるIEEE754だとバイナリイメージで0は、数値+0なんですけどね。
    • good
    • 0
この回答へのお礼

ありがとうございます。

(void*)0もポインター型に代入される0もNULLポインターと同じ値ではあるけれど、それ以外の型の0が必ずしもNULLポインターと同じ値とは限らないと言うことですね。

符号ビットがあるからですよね。

お礼日時:2012/05/12 07:53

(void *)0 がヌルポインタ定数であることは間違いありません. しかし, この規定はあくまで「ソースプログラム上そう書く」というだけであり, 「ビット表現がどうなっているか」については全く関与しません.



一般論として, 「異なる型にキャストした場合」にビット表現が保存されることは保証されていないですよね.
    • good
    • 0
この回答へのお礼

ありがとうございます。
マシンの内部表現とプログラムの字面は違うと言うことですね。

でも、「異なる型にキャストした場合」は残念なことしか起こらない予感しかしないです。

お礼日時:2012/05/12 07:35

特殊なCPUでポインタのビット表現としてオールゼロはあり得ないようなケースでしょうか。


例えばレジスタは32bitだけどアドレス空間は24bitで上位8ビットを別の目的で使っているとか。

なお、NULLポインタは内部表現がどうであれ、数値0と比較すると常に等しいし、ポインタに数値0を(キャストして)代入するとそのアーキテクチャのNULLポインタのビットパターンに変換して代入してくれるはずなので、#define NULL 0 はどんな場合にでも使えるはずです。
    • good
    • 0
この回答へのお礼

ありがとうございます。
思わずLISP処理系の実装を連想してしまいました。(あちらは下位bitを別の目的に転用するという話ですが)

プログラムの字面で0と書いてあるものが、必ずしもマシンの内部でbitがすべて0になった値となっているとは限らないということですね。ポインター型の値との演算を行っていることを検出してコンパイラーが勝手にマシン内でNULLポインターを表す値に変えたマシン語を出すということですね。

int型のポインター型にキャストされている値を+1すると、コンパイル後のマシン語では1でなく、sizeof(int)足されているようなものでしょうか。

お礼日時:2012/05/12 07:07

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