QA@IT

for文の()内での変数宣言について

17994 PV

先日、いつも通りfor文でfor(int i; i<12; i++){}と記述したらコンパイラがエラーを出すようになりました。
以前は普通にできたのですが調べてみても原因がわかりません。
使用言語はCで、コンパイラはGCC ver4.8.1です。
エラーは以下の画像のとおりです。
_____.PNG

回答

そもそもC言語仕様(C99以前)では、関数ローカル変数の初期化(not代入)は
関数の先頭で行うことになっていました。

なので「(int i=0」の部分が関数ローカル変数の初期化であるためコンパイルエラーとなったのでしょう。

おそらく昔からやってたのはこっちなのであ。

void a () {
   int i=-1; (初期化)
   for (i=0 ; i<12 ;i++) { (<-代入)
    処理
   }
}

gcc 4.8系では、ここらへんが厳しくなったのでしょうな。。

編集 履歴 (0)

C言語の規格には古い方からK&R、C89、C99、C11の4種類があります。for (int i=0; i<12; i++) {}が使えるのはC99からです。GCC 4.8系のデフォルトは-std=gnu89(標準C89+GCC拡張)ですので、エラーメッセージに出ているように明示的に-std=c99または-std=gnu99を指定する必要があります。

「以前は普通にできた」とのことですので、できなくなった原因は、何か環境に依存しているのだと思います。例えばmakeを使っているならMakefileに-std=c99等の設定があったのかもしれませんし、環境変数CFLAGSに設定が入っていたのかもしれませんし、IDEを使っているならIDEの設定かもしれません。その辺は環境によると思います。ところが何かのきっかけでGCC 4.8系のデフォルト(-std=gnu89)に戻ってしまったのではないかと思います。

追記

僕の私見ですが、for (int i=0; ...のように書けないのは、変数の宣言位置の問題というよりは変数のスコープの問題ではないかと思います。元々ローカル変数は関数先頭以外でも宣言できます。古いCでfor (int i=0; ...と同じことをしたかったら、下記のようにfor文のまわりをブロックで囲めばよいです。

C89の場合のコード

#include <stdio.h>
int main(void)
{                                               /* 外側のブロック */
    {                                           /* forループ用のブロック */
        int i;
        for (i = 0; i < 10;  ++i) {             /* 内側のブロック */
            printf("%d\n", i);
        }
    }
    return 0;
}

この場合の変数iはforループのスコープ内でだけ有効になります。こうしないとループ変数を適切なスコープで宣言できないことがわかると思います。C99ではforを囲っているブロックが暗黙のブロックとして作られるので、下記のように書いたとき、上でブロックで囲んだのと同じ意味になります。

C99の場合のコード

#include <stdio.h>
int main(void)
{
    for (int i = 0; i < 10;  ++i) {               /* 暗黙のブロックで囲まれている */
        printf("%d\n", i);
    }
    return 0;
}

書き方としてはこの方がすっきりしており、ベターであることがわかると思います。ただし古いCではこの書き方は認められていません。理由は古いCではfor (e1; e2; e3)...のe1、e2、e3は式とされているためだったかと思います(宣言は式ではないので、古いCではe1に宣言は許されない)。e1に宣言を許すためには、コンパイル時に-std=gnu99等をつける必要があります。

$ gcc -std=gnu99 -o loop loop.c

参考ページ

for文 - Wikipedia

追記 (2015/12/06)

(1)C89でfor (int i = 0; ...)が書けない話と(2)C89でローカル変数の宣言位置に制約がある話とはあくまで別の話です。

(1)は既に述べた通りe1が式でなければなりません(C99からは宣言も書けるようになった)。上の「C99の場合のコード」をGCC 4.8系で特別なオプションなしにコンパイルすると、下記のようなエラーとなります。これは質問者さんが画像を貼り付けているエラーと同じものです。

$ gcc -o loop loop.c
loop.c: In function ‘main’:
loop.c:5:5: error: ‘for’ loop initial declarations are only allowed in C99 mode
     for (int i = 0; i < 10;  ++i) {
     ^
loop.c:5:5: note: use option -std=c99 or -std=gnu99 to compile your code

(2)の制約は(繰り返しになりますが)関数先頭ではなく、ブロック先頭である点にも注意して下さい。とはいえ実際にはGCC 4.8系ではGCC拡張により(2)の制約は取り払われているので、ブロック内で先頭以外の位置でも宣言が書け、宣言と文の混在ができます(下記マニュアルを参照)。

https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Mixed-Declarations.html

(1)の例は既に挙げたので、(2)の例を挙げておきます。

test.c

void a()
{
   int j = -1;
   j += 100;
   int i = -1;                                    /* ブロック先頭でなく文の後に宣言 */
   for (i = 0 ; i < 12 ; i++) {
   }
}

int main(void)
{
    a();
    return 0;
}

があったとして、GCC 4.8では

$ gcc -o test test.c

で問題なくコンパイルできます。つまり通常は変数をブロック先頭ではなく利用する直前に宣言することができます。ただし故意にエラーにしたいなら、明示的にC89を指定し、かつ-pedanticを指定すると下記のようにエラーになります。(1)の場合とは異なるエラーになる点に注目して下さい。これはC89の規格に厳密に従った場合は、この位置に宣言を書けない(宣言と文の混在は不可だ)からです。ちなみにC89とC90は同じものです。

$ gcc -std=c89 -pedantic -o test test.c
test.c: In function ‘a’:
test.c:5:4: warning: ISO C90 forbids mixed declarations and code [-Wpedantic]
    int i = -1;
    ^

最後にCでfor (int i = 0;...でなく、外側のブロックで変数iを宣言するのは、スコープが理由になっている場合が多いと思います。つまりforループを抜けた後に変数iを参照したいからだと思います。その場合はそもそもfor (int i = 0; ...とは書けないわけです。

    int i;                                 /* 外側のブロック */
    for (i = 0; i < 12; ++i) {             /* サーチのためのループ */
         if (...)                          /* 見つかったらループを抜ける */
             break;
    }
    if (i < 12) {                          /* 変数iはまだスコープ内にある */
        ....                               /* 見つかった場合の処理 */
    }

逆に変数iのスコープをforループだけに限定したい場合はfor (int i = 0; ...のように書き、明示的に-std=c99または-std=gnu99を指定すべきだといえます。もちろんC89のままで同じことがやりたいなら、最初に挙げたように明示的にforループをブロック{}で囲んでもよいですが、あまり推奨はしません。

編集 履歴 (10)
  • 説明を補いました。
    -
  • 質問者さんはもう興味ないかもしれませんが、他の人も見るかもしれないので、気になっている点に関してあえて追記しました。
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。