本文中に誤りがありましたことをお詫びし、以下に訂正致します。

訂正ページ:P.157 薀蓄野郎が横槍

<誤>
しかし、先に書いたように、要素数の無い配列を初期化した場合、
初期化の数を間違えてもコンパイラが勝手に余った要素を確保する。
しかし、その要素はstatic記憶クラスなら要素の型に応じて0また
はNULLに初期化されるが、その他の記憶クラスでは不定のため何
が設定されているか分からない。コンパイラのみぞ知るのである。
バグの温床になりやすいので、ご注意あれ。
<正>
宣言時に要素数の無い配列を初期化した場合、記述した初期化子
の数を要素数に持つ配列が実体として定義される。従って、初期
化子の数を間違えても気付きにくいため注意が必要だろう。一方、
宣言時に要素数を指定した配列を初期化した場合、要素数未満の
初期化子でも残りは 0 で自動的に初期化される。これもまた、気
付きにくいため注意が必要だ。何れにしてもコンパイラ任せに初
期化を行うのはバグの温床になりやすいので、ご注意あれ。


訂正ページ:P.201〜P.202の差し替え

(リスト ptrArray03改.c)
#include <stdio.h>

int main( void )
{
int a[]={0,10,20};
int *pa;

pa = a;
printf("pa = 0x%08x, *pa = %d\n", pa, *pa);
printf("a[0] = %d, a[1] = %d, a[2] = %d\n", a[0], a[1], a[2]);

printf("*pa++ = %d\n", *pa++); // @
printf("pa = 0x%08x, *pa = %d\n", pa, *pa);
printf("a[0] = %d, a[1] = %d, a[2] = %d\n\n", a[0], a[1], a[2]);

printf("*++pa = %d\n", *++pa); // A
printf("pa = 0x%08x, *pa = %d\n", pa, *pa);
printf("a[0] = %d, a[1] = %d, a[2] = %d\n\n", a[0], a[1], a[2]);

printf("++*pa = %d\n", ++*pa); // B
printf("pa = 0x%08x, *pa = %d\n", pa, *pa);
printf("a[0] = %d, a[1] = %d, a[2] = %d\n\n", a[0], a[1], a[2]);

return 0 ;
}


以降に実行例を示す.


(動作例)
pa = 0x0022ccd0, *pa = 0
a[0] = 0, a[1] = 10, a[2] = 20
*pa++ = 0 // @
pa = 0x0022ccd4, *pa = 10
a[0] = 0, a[1] = 10, a[2] = 20

*++pa = 20 // A
pa = 0x0022ccd8, *pa = 20
a[0] = 0, a[1] = 10, a[2] = 20

++*pa = 21 // B
pa = 0x0022ccd8, *pa = 21
a[0] = 0, a[1] = 10, a[2] = 21


@の結果から *pa++ のように記述すると、取得できる値は a[0] であることがわかる。 pa は式の実行前は a[0] をポイントしているが、実行後は a[1] をポイントしているようだ。つまり、*pa++ のように記述すると、まず *pa が実行されてその次に pa++ が実行されることが理解できると思う。
Aの結果から *++pa のように記述すると、取得できる値は a[2] であることがわかる。 pa は *pa++ の場合と同様に実行後は次の a[2] をポイントしている。つまり、*++pa のように記述すると、まず ++pa が実行されてその次に *pa が実行されている。
Bの結果から ++*pa のように記述すると、取得できる値はAの後の a[2] に +1した値であることがわかる。しかし、 pa は式の前後で変化していない。どうも ++*pa のように記述すると、 *pa により a[2] を指定したことになり、更に ++ により a[2] を +1 し、+ 後の値が式の評価値になることがわかる。
なお,何度も説明しているようにアドレスの絶対値に意味はない,その相対値に着目して欲しい.

改めて演算子の優先順位を確かめると、++ と * は非常に微妙であることが分かった。*pa++ のように ++ をオペランドの後に記述することを後置インクリメント演算子と呼ぶが、規格書では ++ が * より高い優先順位を持つとされている。しかし、@の実行結果から ++ と * は同じ優先順位のように振る舞い、式の評価は左から右へ行われている。
一方、*++pa や ++*pa のように ++ をオペランドの前に記述することを前置インクリメント演算子と呼ぶが、規格書では ++ と * は同じ優先順位である。従って、結合規則の順番どおりにオペランドに近い演算子がまず評価されて、続いてその次に近い演算子が評価される。つまりAでは ++pa が先に評価されるし、Bでは *pa が先に評価されるのであろう。
演算子にまつわる悩ましい点に優先順位、結合規則、評価順序がある。本来ならばこれらの視点から考察するべきだが、素人の筆者には理解するのがむつかしく、説明などできそうにない。今回は、実験的に得られた結果からその評価順序を考察してみた。環境が変われば筆者とは異なる結果が得られるかもしれない。

筆者は先に書いたように演算子同士の優先順位でいつも迷ってしまう。それで、この実験のBのようにポイント先の変数を ++ したい場合、筆者は、

(*pa)++;

または

++(*pa);

と記述する。また、次の要素をポイントして保持する値を参照したい場合は

*(++pa); // C

のように記述するし、今保持する値を参照し次の要素をポイントしたい場合は

*(pa++); // D

のように記述している。このようにして、なるべくどちらを先に実行するかを明確にするようにしている。ただ、素人プログラマである筆者は、Cは

++pa;
*pa;

のように書くし、Dは

*pa;
pa++;

のように書いて、ポインタが指す値の参照とポインタの更新を、二つの文にするようになるべく心がけている。しかし、世の中の多くのプログラムで当たり前のように *pa++ と書かれている例を良く見かける。筆者は迷いも無くよくこのようなプログラムを書けるものだといつも感心している。さらに筆者のような素人がそのプログラムを見た時に悩むことに気付かないのだろうかと不思議にも思う。またこのような優先順位がはっきりしない記述を平気で認めるC言語の仕様にも問題があると思えてならない。

TOP