システムメンテナンスのお知らせ

下記のプログラムですが、

#include <stdio.h>
int main()
{
int n=0;

for(int k=0; k<20; k++) {
n = (n++) & 15;
printf("%d\n", n);
}
}

1~16の間を繰り返して表示してくれそうな気がするのですが、実際は全部0に
なってしまいますが、どうしてなんでしょうか?

gooドクター

A 回答 (5件)

規格での結論は「順序不定なので未定義動作」>#4. たぶん


・値の計算と副作用を切り分ける
・副作用は値の計算のあとに発生し, 次の副作用完了点までのどこに動かしてもいい
・1つのオブジェクトに対して副作用同士, あるいは副作用と値の計算の順序が確定しなければ未定義動作
と考えればいいんじゃないかな.

今の
n = (n++) & 15;
に含まれる「値の計算」と「副作用」は (自明なものを除いて)
(1) n++ の値の計算
(2) n++ の副作用
(3) (n++) & 15 の値の計算
(4) n = (n++) & 15 の値の計算
(5) n = (n++) & 15 の副作用
で, これらの順序は
(1) → (2)
(1) → (3) → (4) → (5)
が決まっているけど (2) と (3)~(5) の順序が決まらず, 特に (2) と (5) の順序が不定なので未定義動作.

ちょっと変えて
int f(int x) { return x; }
という関数がある場合に
n = f(n++) & 15;
という文を考えると
(1) n++ の値の計算
(2) n++ の副作用
(3) f(n++) の値の計算 (呼び出し)
(4) f(n++) & 15 の値の計算
(5) n = f(n++) & 15 の値の計算
(6) n = f(n++) & 15 の副作用
に分解できて, 上と同様
(1) → (2)
(1) → (3) → (4) → (5) → (6)
の順序は決まる. ところが今度は「関数呼び出しの直前に副作用完了点がある」という規則で
(2) → (3)
の順序も付く. つまり全体として
(1) → (2) → (3) → (4) → (5) → (6)
に確定するので合法になるはず.

と, ちょうめんどうなので「どうしても必要」なところ以外に副作用を持つものは入れない方が安全だしあとで読む人にも親切. 今の場合だと
n = (n+1) & 15;
n = (n+1) % 16;
n = (n & 15) + 1;
n = n % 16 + 1;
のどれかでいい (どれかは希望する動作による) んだから, もとより「インクリメントするべき理由」がない.
    • good
    • 0

ループの冒頭で n = 0 の時



(n++) & 15 の式の評価は++ がポストインクリメントだから 
n++ の「式としての評価」は 0。

従って (n++) & 15 の「式としての評価」は 0 になります。

n= ~  の nへの代入では
左辺の式の評価から 0 が代入されることになるし

その一方で ポストインクリメントで nは インクリメントされる
ことになります。

つまり、nに対して
n = 0

n = n + 1
の2つの処理が有るわけです。

規格上どういう順序で実行されるべきか、決まってないのか
知りませんが、とても結果が不明瞭な処理です。

一つの式の中に副作用として同じ変数に対する変更が
2個存在する というのは書くべきではない式でしょう。
    • good
    • 0

>1~16の間を繰り返して表示してくれそうな気がするのですが、


私は0−15の間を繰り返して表示してくれることを期待しますが、

>実際は全部0になってしまいますが、どうしてなんでしょうか?
私のコンパイラでは質問者さんの期待通り、1~16の間を繰り返して表示してくれましたよ

つまり、No2さんの解説のとおり
n = (n++) & 15;
が規格違反の, 不正なプログラムということ
代わりに、
 n++; n &= 15;  // 私の期待した動作

 n &= 15; n++; // 質問者さんが期待した動作
のように規格内の記載にすれば、(人間の)誤解も生じなくてよいと思います
    • good
    • 0

n = (n++) & 15;


のところで, 変数 n をその前後にある 2つの副作用完了点の間で 2回変更している. つまりこのプログラムは規格違反の, 不正なプログラムである. そしてこの違反は「未定義動作」となるため, 規格上いかなる「動作」も規定していない. したがって, 「突然停止する」ことも含め何が起きても文句は言えない.

#1 では「以下の様になります」とあたかも「必ずそうなる」かのように書いてあるが, それは誰も保証していない.
    • good
    • 2

n = (n++) & 15;


の処理を分解すると、以下の様になります
{ int temp=n; n+=1; n=temp&15; }
n が 0→1 と加算されても再び 0 に戻されています

素直に加算と結果の変数を分けましょう。
例)
n = k & 0b1111;
    • good
    • 1

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています

gooドクター

このQ&Aを見た人がよく見るQ&A

人気Q&Aランキング