web-dev-qa-db-ja.com

Cでグローバル変数を使用してもよいのはいつですか?

どうやら、「決してない!常にカプセル化している(たとえそれが単なるマクロであったとしても!)」から「それは大した問題ではない-それらを使用する)まで、さまざまな意見がある。それがより便利なとき。 "

そう。

具体的で具体的な理由(できれば例を挙げて)

  • グローバル変数が危険な理由
  • 代替の代わりにグローバル変数を使用する必要がある場合
  • グローバル変数を不適切に使用したい人のために、どのような選択肢がありますか

これは主観的なものですが、私は1つの答えを選び(私にとっては、すべての開発者がグローバルと持つべき愛/憎しみの関係を最もよく表します)、コミュニティはその答えを真下に投票します。

初心者がこの種の参照を持つことは重要だと思いますが、あなたの回答と実質的に類似している別の回答が存在する場合は混乱しないでください。コメントを追加するか、他の人の回答を編集してください。

-アダム

45
Adam Davis

変数は常に可能な限り小さいスコープを持つべきです。その背後にある議論は、スコープを拡大するたびに、変数を変更する可能性のあるコードが増えるため、ソリューションにさらに複雑さが生じるということです。

したがって、設計と実装で自然に可能であれば、グローバル変数の使用を避けることが望ましいことは明らかです。このため、本当に必要な場合を除いて、グローバル変数を使用しないことを好みます。

他の概念と同様に、グローバル変数は必要なときに使用する必要のあるツールです。実際の意図のみをマスクするいくつかの人工的な構成(ポインターの受け渡しなど)を使用するよりも、グローバル変数を使用したいと思います。グローバル変数が使用される良い例は、組み込みシステムでのシングルトンパターン実装またはレジスタアクセスです。

グローバル変数の過度の使用を実際に検出する方法について:検査、検査、検査。グローバル変数が表示されたときはいつでも、自分自身に質問する必要があります。それはグローバルスコープで本当に必要ですか?

46

グローバル変数を機能させる唯一の方法は、それらに一意であることを保証する名前を付けることです。

その名前には通常、グローバル変数が特に焦点を当てているか意味のあるいくつかの「モジュール」または関数のコレクションに関連付けられたプレフィックスがあります。

これは、変数がそれらの関数に「属している」ことを意味します-それはそれらの一部です。実際、グローバルは通常、他の関数と連携する小さな関数で、同じ.hファイルの同じ名前のプレフィックスで「ラップ」できます。

ボーナス。

そうすると、突然、それはreallyグローバルではなくなります。これは現在、関連する機能のいくつかのモジュールの一部です。

これはalwaysを実行できます。少し考えれば、以前のグローバル変数はすべて、いくつかの関数のコレクションに割り当てられ、特定の.hファイルに割り当てられ、何も壊さずに変数を変更できる関数で分離されます。

「グローバル変数を使用しない」と言うのではなく、「グローバル変数の責任を、最も意味のあるモジュールに割り当てる」と言うことができます。

15
S.Lott

「この範囲が十分に狭い場合、すべてがグローバルです」というこの公案を検討してください。

この時代でも、1回限りのジョブを実行するために非常に迅速なユーティリティプログラムを作成する必要がある可能性はまだあります。

このような場合、変数への安全なアクセスを作成するために必要なエネルギーは、このような小さなユーティリティで問題をデバッグすることによって節約されるエネルギーよりも大きくなります。

これは、グローバル変数が賢明なオフハンドについて考えることができる唯一のケースであり、比較的まれです。小さくて完全に脳の短期記憶内に保持できる有用で斬新なプログラムは、ますますまれですが、まだ存在しています。

実際、プログラムがこれほど小さくなければ、グローバル変数は違法であるべきだと大胆に主張できます。

  • 変数が変化しない場合、それは定数ではなく、変数です。
  • 変数にユニバーサルアクセスが必要な場合は、変数を取得および設定するための2つのサブルーチンが存在し、それらが同期されている必要があります。
  • プログラムが最初は小さく、後で大きくなる可能性がある場合は、プログラムが今日のように大きくなるようにコーディングし、グローバル変数を廃止します。すべてのプログラムが成長するわけではありません! (もちろん、それはプログラマーが時々コードを捨てる気があることを前提としています。)
11
Paul Brinkley

スレッドセーフなコードについて心配していない場合:意味のある場所、つまりグローバルな状態として何かを表現する意味のある場所で使用します。

コードがマルチスレッドの場合:絶対に避けてください。グローバル変数をワークキューまたは他のスレッドセーフな構造に抽象化するか、絶対に必要な場合はそれらをロックにラップします。これらはプログラムのボトルネックである可能性が高いことに注意してください。

8
Dan Lenski

Cのグローバル変数は、複数のメソッドで変数が必要な場合(変数を各メソッドに渡すのではなく)、コードを読みやすくするのに役立ちます。ただし、すべての場所にその変数を変更する機能があり、バグの追跡が困難になる可能性があるため、危険です。グローバル変数を使用する必要がある場合は、必ず1つのメソッドによってのみ直接変更され、他のすべての呼び出し元がそのメソッドを使用するようにしてください。これにより、その変数の変更に関連する問題のデバッグがはるかに簡単になります。

8
Jeff Yates

私は防衛産業で働き始めるまで、「決して」キャンプから来ませんでした。ソフトウェアが動的(Cの場合はmalloc)メモリではなくグローバル変数を使用することを要求するいくつかの業界標準があります。私が取り組んでいるいくつかのプロジェクトの動的メモリ割り当てへの私のアプローチを再考する必要があります。適切なセマフォ、スレッドなどを使用して「グローバル」メモリを保護できる場合、これはメモリ管理への許容可能なアプローチになります。

4
gthuffman

問題の最適化はコードの複雑さだけではありません。多くのアプリケーションでは、パフォーマンスの最適化がはるかに優先されます。しかし、より重要なことに、グローバル変数を使用すると、多くの状況でコードの複雑さが大幅に削減されます。グローバル変数が受け入れ可能な解決策であるだけでなく、好ましい場合もある、おそらく特殊な状況が数多くあります。私のお気に入りの特別な例は、アプリケーションのメインスレッドと、リアルタイムスレッドで実行されるオーディオコールバック関数との間の通信を提供するための使用です。

グローバル変数はマルチスレッドアプリケーションの責任であり、スコープに関係なくすべての変数が複数のスレッドで変更にさらされると潜在的な責任になるため、誤解を招く可能性があります。

グローバル変数は慎重に使用してください。可能な限りデータ構造を使用して、グローバル名前空間の使用を整理および分離する必要があります。

可変スコープはプログラマーに非常に有用な保護を提供しますが、コストがかかる可能性があります。私は今夜​​グローバル変数について書くようになりました。私は経験豊富なObjective-Cプログラマーであり、データアクセスにおけるオブジェクト指向の場所の障壁にしばしば不満を感じるからです。反グローバルな熱心さは、主にオブジェクト指向APIを単独で経験し、システムレベルのAPIの深い実践的な経験とアプリケーション開発におけるそれらの相互作用なしに経験を積んだ、より若い、理論に踏み込んだプログラマーに由来すると主張します。しかし、ベンダーがネームスペースをだらしなく使用すると、私は苛立ちを感じることを認めざるを得ません。たとえば、いくつかのLinuxディストリビューションでは、「PI」と「TWOPI」がグローバルに事前定義されていたため、私の個人コードの多くが壊れていました。

3
ctpenrose

グローバル変数がどのようなコンテキストで使用されるかについても考慮する必要があります。今後、このコードを複製する必要があります。

たとえば、システム内のソケットを使用してリソースにアクセスしている場合です。将来的には、これらの複数のリソースにアクセスする必要があります。答えが「はい」の場合、私はそもそもグローバルから離れているため、大きなリファクタリングは必要ありません。

2
JProgrammer

それは他の通常のように酷使されているようなツールですが、私は彼らが邪悪であるとは思いません。

たとえば、私は本当にオンラインデータベースのように機能するプログラムを持っています。データはメモリに保存されますが、他のプログラムで操作できます。データベースには、ストアドプロシージャやトリガーのように機能する内部ルーチンがあります。

このプログラムには何百ものグローバル変数がありますが、考えてみると、データベースとは何か、膨大な数のグローバル変数があります。

このプログラムは約10年前から多くのバージョンで使用されており、問題にはならなかったので、すぐにもう一度やり直します。

この場合、グローバル変数は、オブジェクトの状態を変更するために使用されるメソッドを持つオブジェクトであることを認めます。したがって、オブジェクトの状態を変更するルーチンにいつでもブレークポイントを設定できるので、デバッグ中にオブジェクトを変更した人を追跡することは問題ではありません。またはさらに簡単に、変更をログに記録する組み込みのログをオンにするだけです。

1
Kevin Gale
  • 使用しない場合:グローバル変数がどのように変更されたかを知る唯一の方法は、それらが宣言されている.cファイル内のソースコード全体(またはすべて。 externの場合はcファイル)。コードにバグがある場合は、ソースファイル全体を検索して、どの関数がいつ変更するかを確認する必要があります。それがうまくいかないときにデバッグするのは悪夢です。私たちはしばしば、ローカル変数の概念の背後にある巧妙な工夫が、優雅に範囲外になる-追跡するのが簡単です
  • 使用する場合:グローバル変数は、その使用率が過度にマスクされておらず、ローカル変数を使用するコストが読みやすさを損なうほど複雑すぎる場合に使用する必要があります。これにより、とりわけ、関数の引数にパラメーターを追加して戻り、ポインターを渡す必要があることを意味します。 3つの典型的な例:popとPushスタックを使用する場合-これは関数間で共有されます。もちろん、ローカル変数を使用することもできますが、追加のパラメーターとしてポインターを渡す必要があります。 2番目の古典的な例は、K&Rの「The C Programming Language」にあり、getch()およびungetchを定義しています。 ()グローバル文字バッファー配列を共有する関数。もう一度、それをグローバルにする必要はありませんが、バッファの使用をめちゃくちゃにするのがかなり難しい場合、追加された複雑さは価値がありますか? 3番目の例は、Arduino愛好家の間で埋め込まれたスペースで見つけるものです。メインloop関数内の多くの関数はすべてmillis()を共有します関数が呼び出された瞬間の時間です。クロック速度は無限ではないので、millis()differになります単一ループ内。それを一定にするには、時間のスナップショットpriorをすべてのループに取り、それをグローバル変数に保存します。これで、タイムスナップショットは、多くの関数からアクセスしたときと同じになります。
  • 代替案:それほど多くありません。特にプロジェクトの最初では、その逆ではなく、可能な限りローカルスコーピングに固執します。プロジェクトの規模が大きくなり、グローバル変数を使用して複雑さを軽減できると感じた場合は、ポイント2の要件を満たしている場合に限ってください。そして、ローカルスコープを使用し、より複雑なコードを使用することは、無責任にグローバル変数を使用することと比較して、それほど危険ではありません。
1
Dean P

複数の関数がデータにアクセスしたり、オブジェクトに書き込んだりする必要がある場合は、グローバル変数を使用する必要があります。たとえば、アプリケーション全体でアクセスする必要のある単一のログファイル、接続プール、またはハードウェア参照などの複数の関数にデータまたは参照を渡す必要がある場合。これにより、非常に長い関数宣言や重複データの大量の割り当てが防止されます。

グローバル変数は、明示的に指示された場合、またはプログラムが終了した場合にのみクリーンアップされるため、絶対的に必要でない限り、通常はnotを使用する必要があります。マルチスレッドアプリケーションを実行している場合、複数の関数が同時に変数に書き込むことができます。バグがある場合、どの関数が変数を変更しているのかわからないため、そのバグを追跡するのはより困難です。また、グローバル変数に一意の名前を明示的に付ける命名規則を使用しない限り、名前の競合の問題に遭遇します。

1
Steropes

定数を宣言するとき。

1
user15039

いくつかの理由が考えられます。

デバッグ/テストの目的(警告-このコードはテストしていません):

#include <stdio.h>
#define MAX_INPUT 46
int runs=0;
int fib1(int n){
    ++runs;
    return n>2?fib1(n-1)+fib1(n-2):1;
};
int fib2(int n,int *cache,int *len){
    ++runs;
    if(n<=2){
        if(*len==2)
            return 1;
        *len=2;
        return cache[0]=cache[1]=1;
    }else if(*len>=n)
        return cache[n-1];
    else{
        if(*len!=n-1)
            fib2(n-1,cache,len);
        *len=n;
        return cache[n-1]=cache[n-2]+cache[n-3];
    };
};
int main(){
    int n;
    int cache[MAX_INPUT];
    int len=0;
    scanf("%i",&n);
    if(!n||n>MAX_INPUT)
        return 0;
    printf("fib1(%i)==%i",n,fib1(n));
    printf(", %i run(s)\n",runs);
    runs=0;
    printf("fib2(%i)==%i",n,fib2(n,&cache,&len));
    printf(", %i run(s)\n",runs);
    main();
};

私はfib2にスコープ付き変数を使用しましたが、これはグローバルが役立つ可能性があるもう1つのシナリオです(永久に実行されないようにデータを格納する必要がある純粋な数学関数)。

一度だけ使用されるプログラム(コンテストなど)、または開発時間を短縮する必要がある場合

グローバルは型付き定数として役立ちます。関数のどこかに、intではなく* intが必要です。

プログラムを1日以上使用する場合は、通常、グローバルを避けます。

1
yingted

私はここで「決して」キャンプにいません。グローバル変数が必要な場合は、少なくとも シングルトンパターン を使用してください。このようにして、遅延インスタンス化の利点を享受し、グローバル名前空間を散らかすことはありません。

0
Nik Reiman

グローバル定数は便利です。プリプロセッサマクロよりも型の安全性が高く、必要に応じて値を変更するのも簡単です。

たとえば、プログラムの多くの部分の動作が状態マシンの特定の状態に依存している場合など、グローバル変数にはいくつかの用途があります。変数を変更できる場所の数を制限する限り、それに関連するバグを追跡する変数はそれほど悪くありません。

グローバル変数は、複数のスレッドを作成するとすぐに危険になります。その場合は、スコープを(最大で)ファイルグローバル(静的と宣言する)変数と、危険な可能性がある複数のアクセスから保護するゲッター/セッターメソッドに制限する必要があります。

0
Adam Hawes