問い1(続々)

【解説】(少しだけ)

間違っている部分もあると思いますので、参考程度に見て下さい。

回答例で「不定」とした部分は、実際には「未定義」という扱いとなり、極端な解釈をすれば全く想定外の結果となり、【C】【F】【G】【I】も含め全て未定義となり全体が木っ端みじんみたいなこともあり得るのかもしれません。
現実的には未定義に対してそこまで暴れまくる実装をしているコンパイラは無いと思うので、それぞれのコンパイラがそれぞれのルールを決めて実装していると考えます。

今回の問題点は、式内で、副作用がある変数に複数回参照している点です。
副作用とは、式の中で演算をしたときに、演算とは別に変数に対して作用を及ぼすことです。
たとえば、次の式があったとき、(これは副作用がある変数(x)に複数回参照していないケース)

 /* x は 0 で初期化済 */
 y = x++;

普通は「yにxの今の値を代入し、xをインクリメントする」という説明になります。

これを演算と副作用に分解します。
使用されている演算子は代入演算の’=’と後置インクリメントの演算’++’の2つです。各演算子の優先度は ‘++’ > ‘=’ です。
演算は優先順位と結合性(左から/右から)に従って評価されます。
【演算】評価順序は①→②→③
①: ‘x++’ を評価して演算前の x の値を返す。つまり 0。
②: ‘y = 0’ を評価して、第1演算項 y に代入されるべき値(つまり第2演算項の値)を返す。つまり 0。
③: ‘0’ という評価値になったが、誰も拾わないので結果は捨てられる。もし、z = y = x++ ならばさらに z = 0 が評価される。
【副作用】A,Bの適用順は未定義
A: x の値を1つ増やす。
B: y の値を 0 にする。

ここで、演算には優先度と結合性がありますが、副作用については式の評価の始まりから、副作用完了点まで間に行えばよいというだけです。
この場合の副作用完了点は終端記号(;)のところになります。
つまり、「*①*②*③*」の任意の場所(*)でAとBは実行されます。この副作用の実行場所がコンパイラによって違う可能性があるわけです。
この式の場合は、xに対して副作用が1回のみしかし要されていないので、どのコンパイラでも結果は同じ(x == 1, y == 0)ということになりそうですが?
しかし、もし、「AB①②③」というように最初に副作用を実行したら y は 1 になるのではないかとふと思いました。単純にそのままの実装なら本当にそうなるかもしれません(???)。

演算項の評価に先立ち全ての副作用を最初に適用するという実装はちょっと除外して話を進めます(複雑になりすぎるので)。

次は、副作用がある変数(x)に複数回参照しているケース。

 /* x は 0 で初期化済、a[]は{9,9}で初期化済 */
 a[x] = x++;

演算子は、配列添え字の'[]’と代入演算の’=’と後置インクリメント演算の’++’。各演算子の優先度は ‘[]’ > ‘++’ > ‘=’ です。
【演算】評価順序は①→②→③→④
①: a[x]を評価して配列 a の場所を特定します。場所は x の値で特定されます。x の値は副作用の実行場所により 0 または 1 となる。評価結果が2つに分かれてしまう。
②: ‘x++’ を評価して演算前の x の値を返す。つまり 0
③: ‘a[0または1] = 0’ を評価して第1演算項 y に代入されるべき値(つまり第2演算項の値)を返す。
④: ‘0’ という評価値になったが、誰も拾わないので結果は捨てられる。もし、z = a[x] = x++ ならばさらに z = 0 が評価される。
【副作用】A,Bの適用順は未定義
A: x の値を1つ増やす。
B: a[0または1] の値を 0 にする。

ということになります。

その他の式も「演算」(値の評価)と「副作用」(変数・オブジェクトの変更)と分けて考え、各幅作用がどこで適用されるかでどう変るかを検証してみて下さい。

一番安全なのは、++, — は単独の式(x++;や–y;など)で使用するのが良いと思います。

「C言語 副作用」や「C言語 副作用完了点」で検索するといろいろ説明しているページが出てきます。

【式と手続き】

プログラムの実行部分は、「式」、「手続き」、「制御文」などありますが、C言語とPascalでは違いがありますね。

C言語の、y = x; は式(代入演算)で、この式自体が値を持ちます。z = y = x; は可。
Pascal言語の Y := X; は代入文で、この文自体は値を持ちません。z := y := x; は不可。

また、変数のインクリメントやデクリメントもC言語では演算子でPascal言語では手続きです。

C言語の x++; はxをインクリメントするだけではなく、これ自体が演算項になり得る。y = x++; は可。
Pascal言語の Inc(X); はXをインクリメントするだけの手続きで、これ自体が演算項にはなり得ない。Y := Inc(X); は不可。

Pascal言語の代入やインクリメントは、「演算と副作用」が発生するのではなく、単純に「作用」を及ぼすものと言えます。

続きは改めて...

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください