インクリメント
インクリメント (英: increment) は、一般には増加・増量・増分・増大という意味だが、コンピュータ用語としては、変数の値を1増やす演算のことである[1]。
逆に、1減らす演算はデクリメント (英: decrement) という[2]。
概要
[編集]インクリメント操作はループにおけるカウンタの増減や、逐次アドレッシングなどで使用頻度が極めて高いため、プロセッサレベルで専用の命令が用意されていることが多い。また、プログラミング言語のレベルでも、インクリメントをするときのための「インクリメント演算子」が用意されていることが多い。さらに、コンパイラのレベルでも、インクリメント演算子に対して最適化してくれる場合が多い。その場合、算術演算子(C言語では+
)や累算代入(複合代入)演算子(+=
)を使うよりもインクリメント演算子(++
)を使ったほうが、コンパイラがプロセッサのインクリメント命令を使うようなコードを生成してくれて高速になる可能性が高い。
しかし、「加算/減算」という概念とは別に「インクリメント/デクリメント」という概念を扱うのは初心者にとって煩雑になり、ある程度慣れたプログラマであっても演算子の評価順序を見誤るとバグの元となる場合がある[注釈 1]。またマイコンレベルでも必須というわけではないため、インクリメント命令を搭載しない場合がある。また、コンパイラの最適化が進んでいた場合、インクリメントを使っても算術演算子を使っても、どちらも同じ(最も速くなる)機械語を生成するので、現代の主要な開発環境およびコンパイラを使っている場合は、あえてインクリメントを使って処理速度を稼ぐ必要はない。そのため、インクリメントという概念をあえて排除する場合もある。例えば、情報処理技術者試験で用いられる仮想計算機COMET IIの命令セットにはインクリメント命令が存在しない。プログラミング言語のPascalにはインクリメント演算子がないが、Object Pascal(Delphi)にはSystem.Inc()
手続きとSystem.Dec()
手続きが存在し、場合によっては最適化されたコードに置き換えられる[4][5]。Free PascalにもInc()
手続きとDec()
手続きが実装されている[6][7]。
なお、PythonやVisual Basic .NETにはインクリメント演算子は存在しないが、++x
というコードは単項の+
演算子を2回適用する+(+x)
と同様に解釈されるため、変数x
の中身が数値の場合は構文エラーにはならず、値をそのまま返す式として評価される。
プロセッサでの扱い
[編集]通常、レジスタに数値を加算するには、1ワードで加算命令とレジスタを表し、もう1ワードで加算する数値を表すので、計2ワードが必要だが、インクリメント命令を使えば1ワードで済む。ただし、プロセッサによっては、インクリメント命令は加算命令よりもオペランドの種類が限られる(たとえばアキュムレータのみに可能など)。
アセンブラの中には、1を加算する加算命令をインクリメント命令に最適化するものもある。
Windows APIではスレッドセーフなアトミック操作のための関数として、InterlockedIncrement()
などが用意されている[8]。.NETにはSystem.Threading.Interlocked
クラスにIncrement()
メソッドなどが用意されている[9]。Javaにはjava.util.concurrent.atomic.AtomicInteger
クラスにincrementAndGet()
メソッドなどが用意されている。これらはコンパイラによってアトミック命令などを使った排他制御に置き換えられる。
高水準言語での扱い
[編集]C言語[10]、C++、C#、Java、JavaScript[1][2]などでは、インクリメント(増分)演算子「++
」が用意されている[注釈 2]。前置インクリメントと後置インクリメントの2種類がある。字句は同じ「++
」だが、前置演算子として使うか(例: ++x
)後置演算子として使うか(例: x++
)で意味が違う。オペランドが数値型(整数型、浮動小数点数型)の場合は1だけ値が変わり、またポインタ型の場合は指し示す対象型の値1個分だけアドレスが変わるのはどちらも同じだが、式としての値が、前置の場合はインクリメントした後の値になり(この意味は+= 1
と同じ)、後置の場合はインクリメントする前の値になる。
y = ++x; // y = (x += 1); と同じ
y = x++; // y = x; x = x + 1; と同じ
C++の演算子オーバーロードでは、通常の記法では前置インクリメントと後置インクリメントを区別できないので、便宜上、後置インクリメントには余分なint
型引数を記述して区別する[11]。ただし、古い[いつ?]C++コンパイラはこの後置インクリメントの宣言に対応していないことがある。
T& class T::operator++() { *this += 1; return *this; } // 前置インクリメントのオーバーロード
T class T::operator++(int) { T old = *this; *this += 1; return old; } // 後置インクリメントのオーバーロード
デクリメント演算子「--
」も同様である。
しかし、前置と後置という些細な違いでプログラムの挙動が変わるので、可読性の低下やバグの要因となることが多い。Swiftには当初C言語と類似のインクリメント演算子とデクリメント演算子があったが、可読性の優先や言語学習コスト低減などの観点から、Swift 3.0で廃止(削除)された[12]。
真理値とインクリメント
[編集]アセンブリ言語(アセンブラ)や初期のプログラミング言語は、真理値を表す専用のデータ型(ブーリアン型)を持たず、整数型で代用することが多かった。整数で代用する場合、通例0を偽 (false) として扱い、また0以外をすべて真 (true) として扱う。C言語はこの仕様を引きずっており、論理演算の結果は基本整数型int
となる。これはANSI C(C89)以前だけでなく、組み込みのブーリアン型_Bool
が追加されたC99以降も変わらない。
このようにブーリアン型として整数型を使用すると、本来インクリメントで記述する必要がない(インクリメントで記述してはいけない)コードにも、誤って使われてしまうことがある。例えば真理値を表すフラグ変数の値を偽から真に変更する場合、通常は変数に何らかの固定値(一般的な代表値としては1)を代入する。しかし、整数型では0をインクリメントすると1になることから、代入ではなくインクリメントでも偽から真に変更することができてしまう。
ソフトウェア工学や安全工学の未発達な時代に、不適切なインクリメント命令の使用が致命的なバグを生み、重篤な事故を引き起こした事例として、放射線療法機器のセラック25が挙げられる。セラック25は1985年から1987年にかけて知られる限り6つの重大な被曝事故を引き起こし、少なくとも5人の患者を死亡させた。事故原因には複数の要因があるが、まずセラック25は従来機に搭載されていた電気機械式の安全保護装置(ハードウェア・インターロック)を取り除き、(当時はハードウェア制御よりも安全だと思われていた)ソフトウェア制御に置き換えてしまっていた。装置を制御するコンピュータPDP-11のオペレーティングシステム[注釈 3]において、安全性を確保するためのソフトウェア・インターロックのひとつに使われていた、Class3と呼ばれるフラグ変数は、値が0以外の場合、装置に何らかの不一致があり、放射線治療を続行できないことを意味するものだった。セットアップテストのルーチンを通過するたびにClass3変数の値がインクリメントされるコードになっていたが、このClass3変数は1バイト(1オクテット)であり、十進数で0から255までの数値しか表現できない。値が255のときにインクリメントしてしまうと、オーバーフローが発生して0に戻ってしまう。この異常動作はセットアップテストを256回通過するたびに発生する。つまり、本来はフラグが真でなければならない場面で偽となり、障害が正しく検出されないというバグが存在していた[14]。これにより、装置の安全チェックがバイパスされてしまい、患者に過度の放射線が照射され、患者が死亡する可能性があった。当初セラック25のメーカーはこのバグに気づかず、「事故は起こりえない」と回答したことが被害を拡大させた。
なお、Cとは異なり、C++には、ISO規格として標準化される前にリリースされた段階でbool
型が存在していた。C++98規格として標準化された時点で、このbool
型は値としてfalse
(0
) あるいはtrue
(1
) のみをとることが保証されるようになった(とはいえ、すべてのC++実装がこの規格で定められた仕様に正しく準拠していたわけではない)。また、bool
型の値をどれだけインクリメントしてもtrue
となるように定められていたが、bool
に対するインクリメントはC++98の時点で非推奨となっていた(この仕様決定にセラック25の教訓が反映されていたかどうかは定かではない)。その後、C++17で、インクリメント演算子「++
」がbool
型に適用できないように規定され、bool
型へのインクリメントは正式に削除された[15]。
JavaやC#のような後発の安全な言語では、ブーリアン型へのインクリメントは登場当初から禁止されている。
ネーミングでの使用
[編集]C++、Notepad++のように、改良版であることを示すためにインクリメント演算子「++」をつけることがある。
「increment」の形容詞形「incremental」は、インクリメンタルサーチやインクリメンタルビルド(増分ビルド)といったソフトウェア機能の名称に使われることもある。
脚注
[編集]注釈
[編集]出典
[編集]- ^ a b “インクリメント (++) - JavaScript”. MDN Web Docs. 2021年5月18日閲覧。
- ^ a b “デクリメント (--) - JavaScript”. MDN Web Docs. 2021年5月18日閲覧。
- ^ EXP30-C. 副作用が発生する式の評価順序に依存しない
- ^ System.Inc - RAD Studio API Documentation
- ^ System.Dec - RAD Studio API Documentation
- ^ Inc / Reference for unit 'System' | Free Pascal
- ^ Dec / Reference for unit 'System' | Free Pascal
- ^ InterlockedIncrement function (winnt.h) - Win32 apps | Microsoft Learn
- ^ Interlocked.Increment Method (System.Threading) | Microsoft Learn
- ^ インクリメント/デクリメント演算子 (C) - cppreference.com
- ^ インクリメント/デクリメント演算子 (C++) - cppreference.com
- ^ Swift 3.0でなぜ「Cスタイルのforループ」「++/--演算子」などの仕様が廃止されたのか - Build Insider
- ^ IEC 62304 実践ガイドブック | 医療機器ソフトウェアに関する各国規制対応のための実例解説 | 一般社団法人 電子情報技術産業協会(JEITA)
- ^ An Investigation of the Therac-25 Accidents -- Part III, Internet Archive
- ^ 非推奨だった bool 型に対するインクリメント演算子を削除 - cpprefjp C++日本語リファレンス