paiza times

paizaがお届けする、テック・キャリア・マネジメント領域における「今必要な情報」を届けるWebメディア

logo

paizaがお届けする、テック・キャリア・マネジメント領域の「今必要な情報」を届けるWebメディア

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

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の場合

f:id:paiza:20210709094225j:plain

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

OracleのThe 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言語の場合

f:id:paiza:20210709094733j:plain

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ラーニング」。オンラインでプログラミングしながらスキルアップできる入門学習コンテンツです。初心者でも楽しくプログラミングの基本を学ぶことができます。

paizaラーニング

そして、paizaでは、Webサービス開発企業などで求められるコーディング力や、テストケースを想定する力などが問われるプログラミングスキルチェック問題も提供しています。

paizaのスキルチェック

スキルチェックに挑戦した人は、その結果によってS・A・B・C・D・Eの6段階のランクを取得できます。必要なスキルランクを取得すれば、書類選考なしで企業の求人に応募することも可能です。「自分のプログラミングスキルを客観的に知りたい」「スキルを使って転職したい」という方は、ぜひチャレンジしてみてください。

paizaのおすすめコンテンツ

Webセキュリティ入門 ハッカー入門 Webセキュリティ講座がスタート!CVは内田真礼さん! Python✕AI 機械学習入門講座 CVに上坂すみれさんを起用!人気の機械学習講座を公開中!
paiza転職 paiza新卒 EN:TRY paizaラーニング 記事内に記載している情報は、記事公開時点でのものとなります。 Copyright Paiza, Inc, All rights reserved.