C言語を学び始めると、i++と++iが頻繁に登場します。
一見どちらも「1増やすだけ」に見えますが、評価されるタイミングが異なるため、書き方によってはバグの原因になります。
本記事では、C言語初心者が安全に使いこなすための具体的な使い分けパターンを、サンプルコードとともに丁寧に解説します。
i++ と ++i とは?
インクリメント演算子とは
C言語では、変数の値を1だけ増やすための演算子として、インクリメント演算子が用意されています。
インクリメント演算子には次の2種類があります。
i++… 後置インクリメント(postfix increment)++i… 前置インクリメント(prefix increment)
どちらも「変数の値を1増やす」という点は同じですが、「いつ増えた値が使われるか」が異なります。
インクリメント演算子を使うと、次のような記述が可能になります。
i = i + 1;をi++;や++i;と短く書ける- ループのカウンタを簡潔に記述できる
- 代入式や関数呼び出しの中で値を1増やしながら使える
ただし、便利だからといって複雑な式の中で乱用すると、意図しない動作やバグの原因になるため注意が必要です。
前置インクリメント(++i)と後置インクリメント(i++)の違い
最も重要な違いは、「その式の中で使われる値が、増える前か増えた後か」です。
i++(後置インクリメント)
まず元の値が式の結果として使われ、その後でiが1増えます。++i(前置インクリメント)
先にiが1増え、増えた後の値が式の結果として使われます。
この違いは、「単独の文として使うか」「式の一部として使うか」で影響度が変わります。
C言語初心者が最初に押さえるポイント
C言語初心者の方は、次のポイントをまず押さえてください。
- 単独の文で使うときは、i++ も ++i もほぼ同じ意味になる
例としてi++;と++i;は、どちらも「i を1増やす」だけです。 - 式の中(代入式・関数引数など)で使うときは、結果の値が変わる
ここでの違いを理解していないと、思ったのと違う値が入ってしまうことがあります。 - 複雑な式の中でインクリメントを多用しない
後ほど詳しく説明しますが、未定義動作と呼ばれる危険な状態を引き起こすことがあります。
これらを意識するだけで、多くの初歩的なバグを未然に防ぐことができます。
i++ と ++i の動きと違いを具体例で理解
i++(後置)の評価順序と値の変化
まずは後置インクリメントi++がどのように動くのか、シンプルな例で確認します。
基本的な動作の例
#include <stdio.h>
int main(void) {
int i = 5;
int x;
// 後置インクリメントの例
x = i++; // ここでの式の結果は「増える前の i の値」
printf("i の値: %d\n", i); // インクリメント後の値が表示される
printf("x の値: %d\n", x); // 式で使われた「元の i の値」が表示される
return 0;
}
i の値: 6
x の値: 5
このコードの動きを、順を追って説明します。
- 最初に
iは5で初期化されます。 x = i++;が実行されると、
2-1.式の結果として「現在の i の値(5)」が使われる
2-2. その結果がxに代入される
2-3. 代入の後でi の値が 1 増え、6 になる- 結果として、
iは6、xは5になります。
「式で使う値は古い値」「変数の実際の中身は後で増える」という2段階の動きがポイントです。
++i(前置)の評価順序と値の変化
次に、前置インクリメント++iの例です。
基本的な動作の例
#include <stdio.h>
int main(void) {
int i = 5;
int x;
// 前置インクリメントの例
x = ++i; // ここでの式の結果は「増えた後の i の値」
printf("i の値: %d\n", i); // インクリメント後の値が表示される
printf("x の値: %d\n", x); // 式で使われた「増えた後の i の値」が表示される
return 0;
}
i の値: 6
x の値: 6
動きを整理すると次のようになります。
- 最初に
iは5で初期化されます。 x = ++i;が実行されると、
2-1.最初に i の値が 1 増え、6 になる
2-2. その「増えた後の i の値(6)」が式の結果として使われる
2-3. その結果がxに代入される- 結果として、
iもxも6になります。
「先に増やしてから、その新しい値を使う」という流れになっています。
代入式での違い
代入式の中にi++や++iを入れると、結果の値が変わることを具体例で確認します。
i++ と ++i の違いを比較するコード
#include <stdio.h>
int main(void) {
int i, a, b;
i = 10;
a = i++; // a には 「増える前の i」が入る
i = 10;
b = ++i; // b には 「増えた後の i」が入る
printf("i++ を使った場合: i = 11, a = %d\n", a);
printf("++i を使った場合: i = 11, b = %d\n", b);
return 0;
}
i++ を使った場合: i = 11, a = 10
++i を使った場合: i = 11, b = 11
この例では、最終的にiはどちらも11になりますが、代入される変数 a と b の値が異なることが確認できます。
a = i++;のときaには10(増える前の値)が代入されます。b = ++i;のときbには11(増えた後の値)が代入されます。
初心者のうちは、「代入式の右辺に i++ や ++i を使うと、右辺に入る値が変わる」という点を丁寧に意識すると理解しやすくなります。
関数引数で使ったときの i++ と ++i の違い
関数の引数としてi++や++iを渡した場合も、同じ考え方で動きが変わります。
関数引数に渡した場合の例
#include <stdio.h>
// 引数として受け取った値を表示するだけの関数
void print_value(const char *label, int value) {
printf("%s: %d\n", label, value);
}
int main(void) {
int i = 3;
// 後置インクリメントを引数に使う
print_value("i++ を渡したとき", i++);
// 前置インクリメントを引数に使う
print_value("++i を渡したとき", ++i);
printf("最終的な i の値: %d\n", i);
return 0;
}
i++ を渡したとき: 3
++i を渡したとき: 5
最終的な i の値: 5
動きを整理すると次の通りです。
- 最初
iは3です。 print_value("i++ を渡したとき", i++);- 関数に渡される値は3(増える前の値)
- その後で
iは4になります。
print_value("++i を渡したとき", ++i);- まず
iが5に増え、その5が引数として渡されます。
- まず
- 最終的に
iは5になっています。
このように、関数に渡される値が違うため、引数として使うときも前置/後置の違いを意識する必要があります。
図解で理解する評価タイミングとメモリのイメージ
前置と後置の違いを、メモリのイメージを使って整理してみます。
ここでは、簡単なテーブルで「式で使われる値」と「最終的な変数の値」の関係をまとめます。
次のような式を考えます。
x = i++;y = ++i;
このときの動きを表にすると次のようになります。
| 初期の i | 式 | 式で使われる値 | 式の実行後の i | 代入される変数 | 代入後の変数の値 |
|---|---|---|---|---|---|
| 5 | x = i++; | 5 | 6 | x | 5 |
| 5 | y = ++i; | 6 | 6 | y | 6 |
「式で使われる値」と「インクリメント後の値」がずれるのが i++であり、「式で使われる値」と「インクリメント後の値」が一致するのが ++iだとイメージすると理解しやすくなります。
for文での i++ と ++i の使い方
for文のカウンタでよく見る i++ と ++i
C言語の入門書やサンプルコードでは、次のようなfor文をよく見かけます。
for (i = 0; i < n; i++) {
/* 繰り返し処理 */
}
または、次のように++iが使われることもあります。
for (i = 0; i < n; ++i) {
/* 繰り返し処理 */
}
この2つは、ループの回数や動作としては同じです。
どちらも「i を 0 から n-1 まで1ずつ増やしながら繰り返す」ループになります。
for(i = 0; i < n; i++) が使われる理由
C言語初心者向けのコードや教科書では、i++を使った書き方が多く採用されています。
その主な理由は次の通りです。
- 見慣れている書き方で、他の人にも理解されやすい
Cだけでなく、C++、Java、JavaScriptなど、多くの言語でfor (i = 0; i < n; i++)という形がよく使われます。 - ループの条件部分では前置/後置の違いが結果に影響しない
for文の増分部分i++や++iは、その値がどこにも代入されないため、どちらを使ってもループ回数は同じです。 - 「i を順番に増やす」という意図が分かりやすい
初心者にとっては、「最初は0で、毎回1ずつ増やす」という流れがイメージしやすくなります。
forループで ++i を使う場合と注意点
一部のスタイルガイドや、C++の世界では++iを好んで使う人もいます。
その背景には、次のような考え方があります。
- 一部のオブジェクト型(イテレータなど)では
++iの方が効率的であることがある
(ただし、C言語の単純なintではほとんど差はありません)
C言語の単純なループでは、性能差はほぼ無視できるレベルです。
そのため、プロジェクト内でスタイルを統一することの方が重要です。
初心者のうちに意識しておきたいポイントは次の通りです。
- for文の増分部では、i++ と ++i のどちらでもよい
- ただしプロジェクトの方針やサンプルコードに合わせると、チーム開発で混乱が少なくなります。
可読性を上げるための書き方のコツ
C言語初心者の方は、とくに「ループの意図が読み手に伝わるか」を重視するとよいです。
そのためのコツをいくつか挙げます。
- インクリメントは for 文の3つ目の部分にまとめる
次のように、ループの最後でi++するのは避けた方が読みやすくなります。/* あまりおすすめしない書き方 */ for (i = 0; i < n; ) { /* 繰り返し処理 */ i++; // どこで i が増えるのか、一瞬考える必要がある }/* おすすめの書き方 */ for (i = 0; i < n; i++) { /* 繰り返し処理 */ } - ループ変数はできるだけ単純に扱う
ループの途中でi++や++iを頻繁に書き換えると、ループの回数が分かりにくくなります。 - インクリメント演算子をループ条件の中で使わない
例えばfor (i = 0; i++ < n;)のような書き方は、慣れていないと非常に読みづらくなります。
「読む人が一度で理解できるか」を基準に書き方を選ぶと、自然とバグも減っていきます。
バグを防ぐ i++ と ++i の使い分けルール
複雑な式の中で i++ と ++i を使わない理由
C言語初心者が特に注意すべきなのは、複雑な式の中でインクリメント演算子を使うことです。
例えば次のような書き方です。
int i = 0;
int a = i++ + ++i; // 非常に危険な書き方の例
このコードは、一見するとiを2回増やして足し算しているように見えますが、C言語ではこのような書き方は未定義動作になる可能性があります(次の節で説明します)。
複雑な式の中で i++ と ++i を両方使うと、どのタイミングで増えるのか、どの値が使われるのかを人間が追い切れなくなります。
その結果、
- コンパイラの最適化によって挙動が変わる
- 実行環境によって結果が変わる
- バグが発生しても原因にたどり着きにくい
といった問題が発生します。
「読み手が一瞬でも迷う書き方」は避けるという意識を持つと、自然と安全なコードに近づきます。
定義されない動作(未定義動作)を避ける書き方
C言語には未定義動作(Undefined Behavior)という概念があります。
未定義動作とは、C言語の仕様上「結果がどうなるか決められていない」動作のことです。
未定義動作が起きた場合、プログラムは何をしてもよいとされています。
つまり、
- たまたま期待通りの値になることもある
- 実行するたびに結果が変わることもある
- 異なるコンパイラや最適化オプションで挙動が変わることもある
- 最悪の場合、プログラムがクラッシュする
といった、非常に危険な状態になります。
典型的な未定義動作の例
次のようなコードは、C言語では未定義動作になります。
#include <stdio.h>
int main(void) {
int i = 0;
// i を 1 つの式の中で複数回変更している危険な例
int a = i++ + i++;
printf("a = %d, i = %d\n", a, i);
return 0;
}
(出力はコンパイラや実行環境によって変わる可能性があります)
このコードがなぜ危険かというと、「どちらの i++ が先に評価されるか仕様で決まっていない」からです。
そのため、コンパイラごとに結果が変わることもあり得ます。
C言語では、「1つの式の中で、同じ変数を複数回変更する」と未定義動作になることが多くあります。
インクリメント演算子を使うときは、次のルールを守ることが大切です。
- 1つの式の中で、同じ変数に対する i++ / ++i を1回だけにする
- インクリメントと別の代入を同じ変数に対して同じ式で行わない
安全な書き換え方の例
先ほどの危険なコードは、次のように分解すれば安全になります。
#include <stdio.h>
int main(void) {
int i = 0;
int a;
// 安全な書き方: 式を分解して、評価順序を明確にする
a = i;
i++;
a += i;
i++;
printf("a = %d, i = %d\n", a, i);
return 0;
}
a = 1, i = 2
実際にはここまで分解しなくても、次のように「1つの式の中では i++ を1回だけ使う」ことを意識すれば十分です。
#include <stdio.h>
int main(void) {
int i = 0;
int a, b;
a = i++;
b = i++;
printf("a = %d, b = %d, i = %d\n", a, b, i);
return 0;
}
a = 0, b = 1, i = 2
このように、処理をいくつかの文に分けるだけで、安全で分かりやすいコードになります。
実務でのおすすめルール
実際の開発現場で、インクリメント演算子によるバグを防ぐために、次のようなルールがよく採用されています。
C言語初心者の方にも、そのままおすすめできるルールです。
- 単独の文として使うときは i++ でも ++i でもよい
例:cst-code>i++;や++i;だけの行など。 - for文の増分部では、プロジェクトのコーディング規約に合わせる
- 特に指定がなければi++ を使うと、他の言語経験者にも分かりやすくなります。
- 代入式や関数引数で i++ / ++i を使う場合は「結果の値の違い」を意識する
a = i++;なのかa = ++i;なのか、どちらの値が必要なのかを明確に考えてから書きます。
- 1つの式の中で、同じ変数に対するインクリメントを複数回使わない
- 例えば
i++ + i++やi = i++ + 1;のような書き方は避けます。
- 例えば
- 少しでも迷ったら、式を分解する
- 1行で書くよりも、2〜3行に分けて書いた方が、バグも少なく保守もしやすくなります。
これらのルールを守るだけで、インクリメント演算子まわりのバグはほとんど防げると言ってよいほど効果があります。
C言語初心者がやりがちなミスとチェックポイント
最後に、C言語初心者がi++と++iでよくやってしまうミスと、そのチェックポイントをまとめます。
よくあるミス1: 代入式で値の違いを意識していない
int i = 5;
int a = i++;
int b = ++i; // ここで i の値が何になっているかを意識していない
このようなコードを書くときは、各行の実行後の i, a, b の値を紙に書いて確認してみると理解が深まります。
よくあるミス2: 1つの式で同じ変数を何度もインクリメントする
int i = 0;
int a = i++ + i++; // 未定義動作の可能性がある危険な書き方
このような式を見つけたら、必ず式を分解して、1つの式でのインクリメントは1回だけに修正しましょう。
よくあるミス3: ループ条件の中でインクリメントを使う
/* 読みにくい例 */
for (i = 0; i++ < n; ) {
/* ... */
}
このような書き方は、慣れていないと「何回ループするのか」がすぐに分かりません。
次のように書き換えると安全です。
for (i = 0; i < n; i++) {
/* ... */
}
自分のコードをチェックするときのポイント
自分の書いたコードを見直すときは、次の点を意識するとよいです。
- i++ / ++i が出てくる行を1つずつ確認し、「増える前の値」か「増えた後の値」かを言葉で説明できるか
- 1つの式の中に、同じ変数に対するインクリメントが複数回登場していないか
- ループの回数や範囲が、自分の意図通りになっているか
このようなチェックを習慣にすると、インクリメント演算子まわりのバグに強いコードを書けるようになっていきます。
まとめ
i++ と ++i は、どちらも値を1増やす演算子ですが、「式の中で使われる値」が異なるため、使い方を間違えるとバグの原因になります。
とくにC言語では、1つの式の中で同じ変数を何度もインクリメントすると未定義動作を招くことがあり危険です。
初心者のうちは、for文の増分部や単独の文以外では、複雑な書き方を避け、必要であれば式を分解することを意識すると安全です。
少しずつ前置/後置の違いに慣れていき、意図がはっきり伝わるコードを書く習慣を身につけてください。
