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.820.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.

目次へ戻る