ヌルポインタ

[戻る]
rikizo 2008/12/16(火) 01:05:17
VC++というよりCの初歩的な質問なのですが、
ヌルポインタについてイマイチ判ってないので
よろしければ教えて下さい。

ある処理系において以下のコードを実行すると(a)を通ります。
これはpは有効なアドレスを指しているにも関わらず
pはヌルポインタと判断されるわけですよね。

ヌルポインタってそういうものなんでしょうか。
空ポインタ定数はいかなるポインタとも一致しない、
という認識だったもので。


/* 変数 io_port は0番地にある */
unsigned char *p = &io_port;  /* pの内部表現も0h */

if (p == 0) {
  /* (a) */
}

rin 2008/12/16(火) 09:38:21
>ある処理系
とは?

仲澤@失業者 2008/12/16(火) 09:47:06
何に悩んでるのかさっぱりわかりませんが、
いわゆるヌルポインタとは一般にはアドレスの0番地を指します。
一般に0番地は有効な番地で、意味のあるコードが書かれている
ことがほとんどです(おっしゃるとおりI/Oの場合もありえます)。

一方、C言語のアルゴリズム上、どうしても「無効なアドレス」を
意味する数値が必要になります。ですがこの数値はコンピュータの
仕組み上ありえません。考えるのが無駄なので0にしているだけです。

8ビットの頃は、RAM未実装のアドレスを無効なアドレスとして使用
していたこともあります。要は「無効である」という意味が通ればよい
のであって、その値がいくらであるかは関係ありません。

tetrapod 2008/12/16(火) 10:55:15
「規格書上の文言」と「実装上の都合で」で選ばれた空ポインタの内部表現値とを
混同してはいけない。特に、ハードウェア実装と絡む場合はこうなる。

「0番地にたまたま有効なメモリ(あるいは I/O ポート)がある」
というのはハードウェア側の実装の話。

一方、Cコンパイラは「0番地に有効なメモリがあるかどうか」は知らない。
お使いのCコンパイラは空ポインタの値の内部表現として0を使っているので、
0番地を指すポインタと空ポインタを区別することができない、ということ。
# 「お使いの」であって「すべての」ではないあたりが重要。

組み込み系のコンパイラであれば、
当該 CPU の設計上0番地に有効な I/O ポートを割り振ることがありうる、と
知っているかもしれない。そういうコンパイラであれば
コンパイルオプション等で空ポインタの内部表現を0以外にできるかもしれない。
マニュアルを探してみるといい。

void read_io_ports(const volatile char * const * table) {
    for (;*table;++table) **table;
}
のようなコードを書かない限り I/O ポートが0番地に割り振られてもかまわない
わけだが、仮に上記のようなコードを書きたいのであれば、それならば
ハードウェア設計のほうに手を加えて0番地にポートを置かないようにすべきだろう。

bun 2008/12/16(火) 12:47:31
なんとなくですが、これで理解できるのかな?

ヌルポインタとはこういうものです。
unsigned char *p = NULL;

rikizoさんの例に
> /* 変数 io_port は0番地にある */
とありますが、変数のアドレスが0番地になることはありえません。

なぜだ?と聞かれると理由は説明できませんが、
そういうものです。

aetos 2008/12/16(火) 13:05:30
> unsigned char *p = NULL;

そういう場合、NULL はどこかで #define されていて、
unsigned char *p = 0;
と同じコードになるんです。

> 変数のアドレスが0番地になることはありえません。

一般的なパソコンの OS の場合なら、ね。
特殊なハードウェアならあり得るということは皆さん言われてますよね。

tetrapod 2008/12/16(火) 14:21:49
ソースコード内の空ポインタ定数の表現と
コンパイル済みバイナリ内の空ポインタの内部表現と
混乱しないようにしておかないといけないぞ。参照
http://www.kouno.jp/home/c_faq/c5.html

ソースコード上での表記 0 ないし NULL は空ポインタ定数を意味する。
これはあくまでソースコード上の表記の話であって、コンパイル済みバイナリでの
空ポインタの内部表現とは話が違う。

rikizo 氏の使っているコンパイラの標準設定ではたまたま
「コンパイル済みバイナリでの空ポインタの内部表現も0」であったわけだ
パソコン上で走るCなど大多数のC/C++処理系でも同様。
ただし、パソコン等、高度OSを持っている処理系においては
「OS側がC言語仕様書+コンパイラ実装者に都合がいいように歩み寄っている」
結果として、0番地に変数や関数がおかれることは無い、ものがほとんど。
だから、空ポインタの内部表現に0を使っても問題が出ない。

組み込み系などOS側の譲歩が期待できないとか、
CPU/OS の構成上が0番地にデータを置くことがごく自然であるとか、
そういう場合には
・ソースコード上に char *p=0; と空ポインタ定数を書いたら
・バイナリコード上では空ポインタの内部表現として0でない値を使う
ことはごく普通にありえるわけだ。
空ポインタの内部表現がどうあれ、ソースコード上は if (p) と書いていい。
ってのが言語規格書の述べているところなわけだ。

「0番地にデータを置く CPU で、空ポインタの内部表現に0を使う」
ようなコンパイラ(+リンカー)は減点1だわな (言語規格書に非合致なので)
そういう(ばぐっていないまでも仕様が良くない)コンパイラを使うのであれば、
ハードウェア設計者がコンパイラに歩み寄って
「0番地にデータ (I/O) を置かない」ようにすればいいだけのこと。
0番地にデータを置かないようにすれば言語規格書に合致するようになるので。

rikizo 2008/12/17(水) 09:01:43
[[解決]]
皆様、お返事ありがとうございました。
私の返事が遅れてすみませんでした。
# パソコンが吹っ飛んでしまいまして…

> rin さん
富士通のSoftuneです。
ここはVC++ラウンジなので、特定の環境の質問をするのは
不適切かと思って伏せたのですが、明記した方が良かったですかね。


> 仲澤@失業者さん
確かに質問内容が不明瞭でした。すみません。
どのポインタとも一致しないはずの空ポインタ定数が
0番地を指すポインタと一致してしまうことが疑問でした。


> tetrapod さん
なるほど、よくわかりました。解決です。

0番地のポートを使わないようにハードウェア側が
歩み寄るという発想はありませんでした。目から鱗です。
でもうちの会社は力関係が「メカ>エレキ>ソフト」って感じなので
ポインタの内部が0番地だから、ここのポートは使わないでくれ、
なんて意見が通るかどうか…。

あと、コンパイラマニュアルを調べましたが、
そのようなオプションは無いようでした。減点1。

杏の里 2008/12/17(水) 10:58:10
>0番地のポートを使わないようにハードウェア側が
>歩み寄るという発想はありませんでした。目から鱗です。
>でもうちの会社は力関係が「メカ>エレキ>ソフト」って感じなので
>ポインタの内部が0番地だから、ここのポートは使わないでくれ、
>なんて意見が通るかどうか…。

組み込み系では、ハードウェアの都合でアドレス空間を決めていると思います。
ソフトウェアではどんなアドレス空間でも対応可能でしょう。
0番地にポートが割り当てられても問題は無いと思います。

昔の8ビットCPUでは、内部IOレジスタが0番地〜XX番地に割り当てられていた物があっ
たように記憶しています。

tetrapod 2008/12/17(水) 12:24:01
> 0番地のポートを使わないようにハードウェア側が歩み寄る
修正が生じうる期間は、ハードウェアよりソフトウェアのほうが長いわけだ。
期間=コスト、なので、もしハードウェアのほうを変更できうるのであれば
変更してもらうほうが製品開発トータルコストは下がる、と説得して味噌。

> 0番地にポートが割り当てられても問題は無いと思います。
問題が無い使い方をする限りには問題ない、というべきだろうな。

・0番地にポート(内部レジスタ)が割り当てられていて変更ができない
・空ポインタの内部表現が0であって変更ができない
であるなら、ソフトウェア作成者が譲歩するしか実際問題として手が無い。
(マイコンを変える、コンパイラを変える、回路を変える、等できない場合)

そういう場合には
注意点:「ポインタを経由して当該ポートをアクセスするコードを書くとバグるよ」
(先に挙げたような read_io_ports のようなコードを書くとまずい)
理由:「***だから」
対策:「***ポートを読み書きしたいのなら***マクロを必ず使うこと」
などと、注意事項と、その原因+対策を明確に文章化して開発者全員に
(ハードウェア担当者にも) 周知徹底しておけば十分だと思う。
# 文書を読まない/守らない奴は戦力外通告ということで

いまどきの 32bit CPU でも0番地にはリセットベクタが置かれていたりするので
0番地に何かコードなりデータなりを書かなきゃならない場合ってのは
ごく普通にある。(SH など)
その0番地をポインタ経由でアクセスする必要があるかどうかを検討し、
必要な場合には「空ポインタの内部表現に一致していても躊躇無く使う」だけのこと。
SH で ROM の checksum を計算する場合なんかに
for (const unsigned char* p=reinterpret_cast<const unsigned char*>(0x00000000);
    p<reinterpret_cast<const unsigned char*>(0x00040000);
    ++p) sum+=*p;
ってのはごく普通に書くコード。

bun 2008/12/18(木) 09:18:29
返事が遅くなりました。

aetosさん、ありがとうございます。
確かに、ほとんど読まずに、超初心者向けのNULLポインタの解説をしてしまいま
した。
失礼しました。

古いスレッドには追加発言できません。