読者です 読者をやめる 読者になる 読者になる

paiza開発日誌

IT/Webエンジニア向け総合求人・学習サービス「paiza」(https://paiza.jp ギノ株式会社)の開発者が開発の事、プログラミングネタ、ITエンジニアの転職などについて書いています。

ツイッターで出題した未定義問題のお詫びと調査と解説について

f:id:paiza:20161227185435j:plain
Photo by Alan Becker Capuyá
青木です。

先日、paizaツイッターアカウント(@paiza_official)で出題した四択問題について、皆様からたくさんのご指摘・ご批判をいただいたので、その経緯と結論をお伝えします。

次のような問題を考えて出題しました。


当初は、それぞれの評価値は順に2, 3, 1, 2となり、3つめの"i++ + i++"の選択肢が答えとなることを想定していました。

ですが、しばらくすると、次のようなリプライやツイートをいただきました。


インクリメント演算子(++)で変数iに数字の1が加えられる順序が、言語仕様では未定義なので、答えられない問題のようです……。

未定義だと、動作が言語の実装に依存しますので、コンパイラなどの環境によって、結果が変わってしまいます。
どのように動作するのかの制限がありあません。(実装に依存する、と述べてましたが、実装に依存して特定の動作が定まるかどうかすら定義されません)

急いで調査したところ、ひとまずJavaでは定義されていることが判明しました。

C言語C++についての詳細はわからないものの、雲行きが怪しそうだったので、訂正ツイートを行いました。

するとさっそく反響が……。


マサカリ投げられたと言われてしまいました。

そして、C++標準化委員会の方からも

C++を想定言語から外していましたが、C++17ではちゃんと決まる……?

途中で問題を変え、ごらんいただいた方を混乱させてしまい、申し訳ございません。

では、この問題が各言語で成立するのかどうなのか、詳しく説明させていただきます。

Javaの場合

まず、Java言語の仕様を調べてみました。

OracleThe Java Language Specification, Java SE 8 Editionの"15.7.1. Evaluate Left-Hand Operand First"で、次のような説明を見つけました。

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

binary operatorの左側の式は、右側の式の前に評価されるようです。

演算子の"+"はbinary operatorですので、正確に式を評価できそうです。

この場合、例えば i++ + ++i では

  • 左側のi++は、0と評価されます。
  • その後、後置インクリメントによって、iの値が1になります。
  • そして、右側の++iが評価されます。前置インクリメントは、評価される前に加算されるので、右側の++iは2と評価されます。
  • よって、左側のi++は0、右側の++iは2と評価されましたので、評価値は2となります。

最初の出題時の想定通りの評価値となりました。

「Java8では、出題した問題の式は評価できるので今回の問題は成り立ちます」

ただし、先ほどの"15.7.1. Evaluate Left-Hand Operand First"の上には次のような記述があります。

It is recommended that code not rely crucially on this specification.
Code is usually clearer when each expression contains at most one side effect, ...

ということで、この仕様に依存しないようにコードを書くべきだと述べられてます。

side effectが文中にいくつも現れるコードは、紛らわしいことこの上ないですね。

ですので、(たぶんやらないかとは思いますが)仕事のプログラムなどで、こんな記述はしない方がよさそうです。

C++の場合

同様に、今度はC++の仕様書を見てみましょう。

isocpp.orgで公開されているC++14のdraftを確認してみました。

1300ページ以上あってとても一度にすべてを読めたものではないので、ひとまず"increment"で検索してみました。

すると、1.9.15のExampleに、次のような記述を見つけました。

i = i++ + 1; // the behavior is undefined

i++の後ろに、+で数値をつないだ場合、動作が定義されてないようです。
i++と同時にiへの代入を行う場合の動作が定義されてないようです。

C++14では、出題した問題の式は評価できないので今回の問題は成り立ちません」

しかし、先ほどのツッコミが。

C++では定義されているのですね!急いでC++17の仕様書を確認しに行きました。

isocpp.orgの最新のドラフトを見ます。

1.9.18ののExampleにそれらしき記述を見つけました。

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

むむむ、i++ +1は定義されるようになったけど、i++ + iは未定義とあります……。i++ + iが未定義なら、i++ + ++iも未定義な気がします……。
i = i++ +1は定義されるようになったけど、i = i++ + iは未定義とあります……。i = i++ + iが未定義なら、i++ + ++iも未定義な気がします……。

よくわからなくなってきたので、ご指摘いただいた江添さんのブログに凸りました。(質問コメントしているのは私です)
cpplover.blogspot.jp

お返事いただけました。

やっぱり未定義だそうです。

C++14でもC++17でも、出題した問題の式の評価が未定義なので今回の問題は成り立ちません」

ただ、C++17ではside effectのある式の評価が見直されてるようなので、今後は変わっていくかもしれませんね。

C言語の場合

C11の仕様書のドラフトを確認します。

6.5.2.4 Postfix increment and decrement operatorsの2に次のような記述がありました。

The side effect of updating the stored value of the operand shall occur between the previous and the next sequence point.

どうやら、"sequence points"の範囲の中でside effectが評価されるようです。

"sequence points"の定義に"+"演算子は含まれません。(sequence pointsについては、同仕様書のAnnex Cに書かれています。Wikipediaのsequence pointsに関する説明の方が具体例が載ってて分かりやすいかもしれません)

ですので、i++ + ++iの式の中で、i++によって、後置インクリメントされるタイミイングが決められておらず、++iを評価する前と後のどちらで、iの値が1増加するのか定めることができません。
iの値が1増加するタイミイングが、++iを評価する前と後のどちらなのかが、ここからだけでは読み取れません。

さらに、Annex JのJ.2 Undefined behaviorのリストに、次のような記述があります。

A side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object (6.5).

同じオブジェクトに対しての異なるside effectによって順序が決まらない場合は、未定義とのことです。

ということは、「C11では、出題した問題の式の評価が未定義なので今回の問題は成り立ちません」

■まとめ

  • Java8では定義されていて、出題した問題は解ける。(ただし推奨されている書き方ではない)
  • C++14とC++17では未定義なので、出題した問題は解けない。
  • Cでは未定義なので、出題した問題は解けない。

というわけで、ツイッターで訂正させていただきました通り、今回の問題は想定言語はJavaのみとなります。

詳細を確認しないままツイートして、皆様を混乱させてしまいまして申し訳ございません。また、鋭いご指摘をいただいた皆様、ありがとうございました。




paizaではスキルのあるエンジニアがきちんと評価されるようにし、技術を追い続ける事が仕事につながるようにする事で、日本のITエンジニアの地位向上を図っていければと考えています。特にpaizaではWebサービス提供企業などでもとめられる、システム開発力や、テストケースを想定できるかの力(テストコードを書く力)などが問われる問題を出題しています。

テストの結果によりS,A,B,C,D,Eの6段階でランクが分かります。自分のプログラミングスキルを客観的に知りたいという方は是非チャレンジしてみてください。

また数多くの自社サービス提供企業、webサービス提供企業の求人を掲載しています。

http://paiza.jp

プログラミング入門講座|paizaラーニング

PHP入門編Ruby入門編Python入門編Java入門編JavaScript入門編C言語入門編C#入門編アルゴリズム入門編