4章.ポインター
4.2:
ポインターを宣言して、そのポインター用の領域を確保しようとして
いる。でもうまくいかない。以下のコードのどこがおかしいのか。
char *p
*p = malloc(10);
A:
君が宣言したポインターはpであって*pではない。ポインターにどこ
かを指させるにはポインターの名前を使うだけでいい。
p = malloc(10);
指した先のメモリーを操作するときに初めて*を間接演算子として使う。
*p = 'H';
質問1.21,7.1,8.3も参照のこと。
References: CT&P Sec. 3.1 p. 28.
4.3:
*p++はpを増分するか。それともpが指すものを増分するのか。
A:
*, ++, --のような単項演算子は、右から左に結び付ける。だから
*p++はpを増分する(そして増分する前にpが指していた値を返す)。p
が指していた値を増分するには、(*p)++を使う(副作用が起こる順序
が問題でないなら++*pでもいいだろう)。
References: K&R1 Sec. 5.1 p. 91; K&R2 Sec. 5.1 p. 95; ANSI
Sec. 3.3.2, Sec. 3.3.3; ISO Sec. 6.3.2, Sec. 6.3.3; H&S
Sec. 7.4.4 pp. 192-3, Sec. 7.5 p. 193, Secs. 7.5.7,7.5.8 pp. 199-
200.
4.5:
charのポインターがあって、そのポインターがたまたまintを指して
いた。intの次へポインターを進めたい。なぜ以下の式ではうまくい
かないのか。
((int *)p)++;
A:
C言語でキャスト演算子は「ビットが別の型を持っているふりをして、
そういう風に扱ってやろう」ということを意味していない。キャスト
は変換演算子であって、それは右辺値を生みだすと定義されている。
右辺値であるとするなら、代入することも++で足し算することもでき
ないことになる(pccから派生したコンパイラやgccの拡張機能が上の
ような式を受け付けることは例外である)。思っていることを式にあ
らわそう。以下のようにする。
p = (char *)((int *)p + 1);
あるいは(pはchar *なので)単に
p += sizeof(int);
可能なときはいつでも、最初から適切なポインター型を選ぶべきであ
る。ある型を別の型とみなそうとしてはいけない。
References: K&R2 Sec. A7.5 p. 205; ANSI Sec. 3.3.4 (esp.
footnote 14); ISO Sec. 6.3.4; Rationale Sec. 3.3.2.4; H&S
Sec. 7.1 pp. 179-80.
4.8:
ポインターを引数として取って、そのポインターを初期化することに
なっている関数がある。
- void f(ip)
- int *ip
- {
- static int dummy = 5
- ip = &dummy
- }
けれど以下のように呼んだら、
int *ip
f(ip);
呼んだ側のポインターの値は変わらなかった。
A:
本当に関数は、君が初期化してくれてると思ったものを、初期化して
いると自信があるか。C言語の引数は値渡しであることをお忘れなく。
呼ばれた側の関数は、ポインターの渡されたコピーを変更するだけで
ある。お望みの結果を得るには、ポインターのアドレスを渡すか(関
数はポインターへのポインターを受けとることになる)、関数がポイ
ンターを返すようにする。
質問4.9,4.11も参照のこと。
4.9:
参照呼び出しに使うため汎用のポインターを関数に渡すのに、
void **を使うことができるか。
A:
移植性まで考れば不可能である。C言語には汎用のポインターへのポ
インター型は存在しない。void *が汎用のポインターとして振る舞う
のは、他の型のポインターをvoid *に設定したり参照するときに、自
動的に変換が行われるからである。void *以外の何かを指している
void **を使って間接参照しようとすると、この変換は実行できない
(隠れた正しいポインターの型がわからない)。
4.10:
以下の関数がある。
extern int f(int *);
これは整数へのポインターを引数として受け取る。どうやれば定数を
参照渡しすることができるか。以下のような呼び出しは、
f(&5);
うまくいかないようだ。
A:
一発では無理だ。一時変数を定義して、その変数のアドレスを関数に
渡さなければならない。
int five = 5
f(&five);
質問2.10, 4.8, 20.1も参照のこと。
4.11:
Cにも"参照渡し"が用意されてるか。
A:
まさか。厳密にいえば、Cはいつも値渡しである。手をわずらわして
参照渡しを真似ることはできる。ポインターを引数として取る関数を
定義し、その関数を呼ぶときに引数に&演算子を使う。あるいは関数
に配列を渡すと(かわりにポインターを渡すことで。質問6.4等を参照)
コンパイラーは実質的に参照渡しの振りをしてくれる。しかしCは正
式の参照渡しや、C++の参照パラメーターに本当に匹敵するものは持
ち合わせていない。(ただし、関数に似せたプリプロセッサーのマク
ロは、一種の"名前呼び出し"を提供する。)
質問4.8と20.1も参照のこと。
References: K&R1 Sec. 1.8 pp. 24-5, Sec. 5.2 pp. 91-3; K&R2
Sec. 1.8 pp. 27-8, Sec. 5.2 pp. 91-3; ANSI Sec. 3.3.2.2, esp.
footnote 39; ISO Sec. 6.3.2.2; H&S Sec. 9.5 pp. 273-4.
4.12:
関数を呼ぶのに、ポインターを通す方法をみたことがある。どうなっ
てるの。
A:
もともとは関数へのポインターは*演算子を使って(かつ優先順位をはっ
きりさせるためのおまけのかっこを使って)"本物の"関数へ変換しな
ければならなかった。
int r, func(), (*fp)() = func
r = (*fp)();
また以下のように主張することも可能である。関数は常にポインター
経由で起動され"本物の"関数の名前は常にポインターに成り下がる
(式の中で、初期化のときと同じ様に。質問1.34参照)から何も問題は
ない。こういう裏付けがpccを通して広がりANSI規格にも採用された。
すなわち、
r = fp();
はfpが関数の名前でも、関数へのポインターでも正しく問題なく動く
(この書き方は始めからあいまいさの入る余地の無いものであった。
関数へのポインターに引数が続いていれば、そのポインターが指して
いる関数を呼ぶしか仕方がないもの)。*を書くことは害がないし、今
まで通り許される(許されるどころか、古いコンパイラとの互換性が
重要であれば使うことを勧める)。
質問1.34も参照のこと。
References: K&R1 Sec. 5.12 p. 116; K&R2 Sec. 5.11 p. 120; ANSI
Sec. 3.3.2.2; ISO Sec. 6.3.2.2; Rationale Sec. 3.3.2.2; H&S
Sec. 5.8 p. 147, Sec. 7.4.3 p. 190.