web-dev-qa-db-ja.com

Cでの変数宣言の配置

Cでは、すべての変数を関数の先頭で宣言する必要があると長い間考えていました。 C99の規則はC++の規則と同じですが、C89/ANSI Cの変数宣言の配置規則は何ですか?

次のコードは、gcc -std=c89およびgcc -ansiで正常にコンパイルされます。

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

cおよびsの宣言は、C89/ANSIモードでエラーを引き起こすべきではありませんか?

120
mcjabberz

G89では、C89またはANSI標準の一部ではないにもかかわらず、GCCがGNU拡張として許可しているため、正常にコンパイルされます。これらの標準に厳密に従う場合は、-pedantic 国旗。

137
mipadi

C89の場合、すべての変数をscopeブロックの先頭で宣言する必要があります。

だから、あなたのchar c宣言は、forループスコープブロックの先頭にあるため有効です。しかし char *s宣言はエラーになります。

74
Kiley Hykawy

ブロックの先頭で変数宣言をグループ化することは、古いプリミティブなCコンパイラの制限による可能性があります。最新のすべての言語は、ローカル変数の宣言を、それが最初に初期化された最新の時点で推奨し、場合によっては強制することさえあります。これは、誤ってランダムな値を使用するリスクを取り除くためです。宣言と初期化を分離すると、可能であれば「const」(または「final」)を使用できなくなります。

C++は残念なことに、Cとの後方互換性のために古いトップ宣言の方法を受け入れ続けています(1つのC互換性は他の多くのものか​​ら引きずり出されます...)

  • C++参照の設計では、このような最上位のグループ化さえ許可されていません。
  • C++ローカルオブジェクトの宣言と初期化を分離する場合、余分なコンストラクターの費用は無料です。引数なしのコンストラクタが存在しない場合は、再び両方を分離することもできません!

C99は、この同じ方向にCを移動し始めます。

ローカル変数が宣言されている場所が見つからないことが心配な場合は、より大きな問題があることを意味します。囲みブロックが長すぎるため、分割する必要があります。

https://www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

29
MarcH

構文上の観点ではなく、保守性の観点から、少なくとも3つの考え方があります。

  1. 関数の先頭ですべての変数を宣言して、1つの場所に配置し、包括的なリストを一目で確認できるようにします。

  2. すべての変数は、最初に使用した場所にできるだけ近い場所で宣言するので、whyがそれぞれ必要であることがわかります。

  3. 最も内側のスコープブロックの先頭ですべての変数を宣言します。これにより、できるだけ早くスコープから外れて、コンパイラーがメモリを最適化し、意図しない場所で誤って使用した場合に通知できるようになります。

私は一般的に最初のオプションを好む、なぜなら他の人はしばしば宣言のためのコードを探し回ることを私に強いるからだ。すべての変数を事前に定義することで、デバッガーからの初期化と監視が容易になります。

小さいスコープブロック内で変数を宣言することもありますが、これには正当な理由があるためです。 1つの例は、fork()の後、子プロセスだけが必要とする変数を宣言する場合です。私にとって、この視覚的なインジケータは、その目的を思い出させるのに役立ちます。

23
Adam Liss

他の人が述べたように、「CC89」モードであっても、「pedantic」チェックを使用しない限り、GCCはこの点で(および、呼び出される引数に応じて他のコンパイラも)許容します。正直に言うと、物足りにしない理由はあまりありません。品質の高い最新のコードは、常に警告なしでコンパイルする必要があります(または、間違いを犯す可能性があるとコンパイラに疑わしい特定のことをしていることがわかっている場合はほとんどありません)。

C89では、各スコープ内の他のステートメントの前に変数を宣言する必要があります。後の標準では、より使いやすい宣言(より直感的で効率的なことができます)、特に「for」ループでのループ制御変数の同時宣言と初期化が許可されています。

6
Gaidheal

すでに述べたように、これには2つの考え方があります。

1)年が1987年であるため、機能の最上位ですべてを宣言します。

2)最初の使用に最も近い、可能な限り小さい範囲で宣言します。

これに対する私の答えはDO BOTHです!説明させてください:

長い関数の場合、1)リファクタリングが非常に困難になります。開発者がサブルーチンの考え方に反対しているコードベースで作業している場合、関数の開始時に50の変数宣言があり、それらの一部はforループの "i"である可能性があります。関数の下部。

そのため、私はこれからPTSD宣言を作成し、オプション2)を宗教的に実行しようとしました。

短い機能のために、オプション1に戻りました。関数が十分に短い場合、ローカル変数はほとんどありません。関数は短いため、関数の先頭に配置すると、最初の使用に近くなります。

また、先頭で宣言したいが初期化に必要な計算を行っていない場合の「宣言してNULLに設定」のアンチパターンは、初期化する必要があるものが引数として受け取られる可能性が高いため解決されます。

だから今私の考えでは、関数の先頭で、できるだけ最初に使用するように宣言する必要があります。両方!そして、それを行う方法は、適切に分割されたサブルーチンを使用することです。

ただし、長い関数で作業している場合は、メソッドを抽出する方が簡単になるため、最初に使用することに最も近いものを配置してください。

私のレシピはこれです。すべてのローカル変数について、変数を取得し、その宣言を一番下に移動し、コンパイルしてから、コンパイルエラーの直前に宣言を移動します。それが最初の使用です。すべてのローカル変数に対してこれを行います。

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

ここで、宣言の前に開始するスコープブロックを定義し、プログラムがコンパイルされるまで終了を移動します

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

Fooを使用するコードがいくつかあるため、これはコンパイルされません。コンパイラはfooを使用しないため、barを使用するコードを通過できたことがわかります。この時点で、2つの選択肢があります。機械的な方法は、コンパイルするまで "}"を下に移動することです。他の選択肢は、コードを検査し、順序を次のように変更できるかどうかを判断することです。

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

順序を切り替えることができる場合、一時的な値の寿命が短くなるため、おそらくそれが望みです。

別の注意点として、fooの値はそれを使用するコードのブロック間で保持する必要がありますか、それとも両方の異なるfooである可能性があります。例えば

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}

これらの状況には、私の手順以上のものが必要です。開発者は、コードを分析して何をすべきかを判断する必要があります。

しかし、最初のステップは最初の用途を見つけることです。視覚的にはできますが、宣言を削除し、コンパイルして最初の使用よりも上に戻す方が簡単な場合があります。その最初の使用がifステートメント内にある場合は、それをそこに置き、コンパイルされるかどうかを確認します。その後、コンパイラは他の用途を識別します。両方の用途を含むスコープブロックを作成してください。

この機械的な部分が完了すると、データの場所の分析が容易になります。変数が大きなスコープブロックで使用されている場合、状況を分析し、2つの異なるもの(2つのforループに使用される「i」など)に同じ変数を使用しているかどうかを確認します。用途が無関係である場合、これらの無関係な用途ごとに新しい変数を作成します。

0