アジの開きを閉じる。

競プロ(AtCoder)中心のブログ

cout<<(a--+a)<<endl; 出力されるのは?

Twitterに投稿したクイズの解説です。

元ツイートのリプに記載した内容をまとめ直しました。

私はC++もといプログラミング自体勉強中の身なので,間違い等あれば指摘してくださると幸いです。

 

目次

 

 

正解

正解は,未定義動作で一意に定まらないので「その他」です。

ただし,10も9も(もしかしたら11も)出力される場合があるので,問題文を「出力されうるのは?」と解釈するならばそれらも正解になります。

 

動作

1行目の動作はint型で宣言したaに5を代入しているだけです。

問題は2行目の動作です。

以下の説明では"a--"という表記は+演算子の左側のオペランドを表し,単に"a"という表記は右側のオペランドを表します。

 

2行目の動作は,評価順序がカギになります。

cppreferenceの"Order of evaluation"では未定義動作の項目に,次のように書かれています。

2) If a side effect on a memory location is unsequenced relative to a value computation using the value of any object in the same memory location, the behavior is undefined.

つまり,ひとつの完全式において「あるメモリ位置への副作用が,同じ位置にあるオブジェクトを利用した値の計算と比較して順序づけられていなければ未定義動作になる」ということです。

 

もとの問題の2行目では,オブジェクトaを利用した値の計算がa--とaで行われます。

また,a--で後置デクリメントという副作用が生じます。

 

この副作用とaの値の計算は順序づけされていないため,上記に該当し,未定義動作になります。

 

C++のバージョンについて

C++のバージョンが指定されていない」というお声を頂きました。

出題時には全く想定していませんでしたが、調べてみたところバージョンによらず未定義動作になるようです。

 

評価順序の規則はC++11をきっかけに変更されました。

C++11より前ではSequence point rules,以後では"Sequenced before" rulesです。

先述の動作説明はC++11以後の"Sequenced before" rulesに基づいています。この規則はC++17で規則が追加されたことによりそれまで未定義動作だったもののいくつかが未定義動作でなくなりましたが,この問題の動作に関しては未定義動作のままです。

 

C++11より前のSequence point rulesでは,未定義動作の項目に次の内容が書かれています。

2) Between the previous and next sequence point, for any object in a memory location, its prior value that is modified by the evaluation of the expression must be accessed only to determine the value to be stored. If it is accessed in any other way, the behavior is undefined.

つまり,「連続するシーケンスポイント(評価順序の区切り)間で,式の評価によって値が変更されるすべてのオブジェクトについて,変更前の値は変更後の値を決定するためにのみアクセスされなければならない。他の方法でアクセスされるなら未定義動作になる」と言うことです。

 

もとの問題の2行目では,a--でオブジェクトaの値の変更(デクリメント)が生じています。一方,aでもオブジェクトaへアクセスしていますが,このアクセスで得たオブジェクトaの値はaに新たに格納される値を決定するために使用されておらず,標準出力に使用されています。

 

よって,上記に該当し,やはり未定義動作になります。

 

人によっては答えにくいクイズとなった理由

未定義動作だと見抜いていた人には「選択肢のどれを選べば良いの?」と,戸惑ってしまうクイズでした。

そうなった理由は,私が未定義動作が正解だと思わずに出題したためです。

 

出題時は「オペランドのアクセスは左から順に行われる」という思い込みをしていました。

その下で,「後置デクリメント(以下,後置インクリメントも同様)は";"の位置で行われると思っている人がいそうだけど,実はa--の処理でデクリメントが行われる。だからその直後にオブジェクトaにアクセスするとデクリメント後の値が返ってくる。だからこの問題で出力されるのは10(=5+5)ではなく9(=5+4)になる。」と考え,軽いノリで出題しました。

(実際,私も後置デクリメント・後置インクリメントのタイミングについて誤って覚えていました)

 

そのため,選択肢に「10」「9」を用意し,完全ダミーの「11」と,念のために「その他」も加えました。

このとき未定義動作の可能性を想定していなかったため,結果的に人によっては答えにくいクイズとなりました。

 

「未定義動作だと思った場合はどの選択肢を選ぶべき?」というお声を頂いたときに,未定義動作の可能性を疑い,いろいろ調べたところで自分の当初の思い込みが誤りであったことが分かりました。

そして,正解が未定義動作であることが分かりました。

 

参考文献

Order of evaluation - cppreference.com

c++ - Undefined behavior and sequence points - Stack Overflow 

 

それでは。