VC++というよりCの初歩的な質問なのですが、
ヌルポインタについてイマイチ判ってないので
よろしければ教えて下さい。
ある処理系において以下のコードを実行すると(a)を通ります。
これはpは有効なアドレスを指しているにも関わらず
pはヌルポインタと判断されるわけですよね。
ヌルポインタってそういうものなんでしょうか。
空ポインタ定数はいかなるポインタとも一致しない、
という認識だったもので。
/* 変数 io_port は0番地にある */
unsigned char *p = &io_port; /* pの内部表現も0h */
if (p == 0) {
/* (a) */
}
>ある処理系
とは?
何に悩んでるのかさっぱりわかりませんが、
いわゆるヌルポインタとは一般にはアドレスの0番地を指します。
一般に0番地は有効な番地で、意味のあるコードが書かれている
ことがほとんどです(おっしゃるとおりI/Oの場合もありえます)。
一方、C言語のアルゴリズム上、どうしても「無効なアドレス」を
意味する数値が必要になります。ですがこの数値はコンピュータの
仕組み上ありえません。考えるのが無駄なので0にしているだけです。
8ビットの頃は、RAM未実装のアドレスを無効なアドレスとして使用
していたこともあります。要は「無効である」という意味が通ればよい
のであって、その値がいくらであるかは関係ありません。
「規格書上の文言」と「実装上の都合で」で選ばれた空ポインタの内部表現値とを
混同してはいけない。特に、ハードウェア実装と絡む場合はこうなる。
「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番地にポートを置かないようにすべきだろう。
なんとなくですが、これで理解できるのかな?
ヌルポインタとはこういうものです。
unsigned char *p = NULL;
rikizoさんの例に
> /* 変数 io_port は0番地にある */
とありますが、変数のアドレスが0番地になることはありえません。
なぜだ?と聞かれると理由は説明できませんが、
そういうものです。
> unsigned char *p = NULL;
そういう場合、NULL はどこかで #define されていて、
unsigned char *p = 0;
と同じコードになるんです。
> 変数のアドレスが0番地になることはありえません。
一般的なパソコンの OS の場合なら、ね。
特殊なハードウェアならあり得るということは皆さん言われてますよね。
ソースコード内の空ポインタ定数の表現と
コンパイル済みバイナリ内の空ポインタの内部表現と
混乱しないようにしておかないといけないぞ。参照
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番地にデータを置かないようにすれば言語規格書に合致するようになるので。
皆様、お返事ありがとうございました。
私の返事が遅れてすみませんでした。
# パソコンが吹っ飛んでしまいまして…
> rin さん
富士通のSoftuneです。
ここはVC++ラウンジなので、特定の環境の質問をするのは
不適切かと思って伏せたのですが、明記した方が良かったですかね。
> 仲澤@失業者さん
確かに質問内容が不明瞭でした。すみません。
どのポインタとも一致しないはずの空ポインタ定数が
0番地を指すポインタと一致してしまうことが疑問でした。
> tetrapod さん
なるほど、よくわかりました。解決です。
0番地のポートを使わないようにハードウェア側が
歩み寄るという発想はありませんでした。目から鱗です。
でもうちの会社は力関係が「メカ>エレキ>ソフト」って感じなので
ポインタの内部が0番地だから、ここのポートは使わないでくれ、
なんて意見が通るかどうか…。
あと、コンパイラマニュアルを調べましたが、
そのようなオプションは無いようでした。減点1。
>0番地のポートを使わないようにハードウェア側が
>歩み寄るという発想はありませんでした。目から鱗です。
>でもうちの会社は力関係が「メカ>エレキ>ソフト」って感じなので
>ポインタの内部が0番地だから、ここのポートは使わないでくれ、
>なんて意見が通るかどうか…。
組み込み系では、ハードウェアの都合でアドレス空間を決めていると思います。
ソフトウェアではどんなアドレス空間でも対応可能でしょう。
0番地にポートが割り当てられても問題は無いと思います。
昔の8ビットCPUでは、内部IOレジスタが0番地~XX番地に割り当てられていた物があっ
たように記憶しています。
> 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;
ってのはごく普通に書くコード。
返事が遅くなりました。
aetosさん、ありがとうございます。
確かに、ほとんど読まずに、超初心者向けのNULLポインタの解説をしてしまいま
した。
失礼しました。