web-dev-qa-db-ja.com

Cのファイルスコープの「静的」変数は「extern」グローバル変数と同じくらい悪いですか?

Cでは、(スタイルの問題として)ファイルスコープstatic変数を使用することがよくありますが、C++ではプライベートクラスメンバー変数を使用します。マルチスレッドプログラムにスケーリングする場合は、単にthread_local C11または長い間サポートされている拡張機能__threadよく合います。 struct内にすべてを入れ、そのstructへのポインタを最初の引数として取る関数のセットを作成することで、CでもC++とまったく同じことができることを知っています。一部のライブラリは、これを広範囲にわたって実行します。しかし、私の個人的なスタイルは、必要に応じてstructをできるだけ小さくすることです。

「グローバル」変数が非常に悪いと主張する人々をよく読んだり聞いたりします。私はそれらの理由を追っていますが、彼らの議論のほとんどはC用語のexternグローバル変数に関連しているようです。彼らの言うことは確かに真実です。物事を大幅に簡略化し、追跡が容易な場合、プログラム全体でextern宣言された変数の1つまたは2つを使用することがありますが、さらに先に進むとプログラムが予測不可能になります。

static変数はどうですか? 「実際の」グローバル変数と同じ問題がまだありますか?多分私はこの質問をして私がしていることが正しいと思うなら続ける必要さえないかもしれませんが、今日私は別の「グローバル変数は悪い」種類の投稿を見た、そしておそらくこれはおそらく正しいとここに来ましたそのような質問のための場所。あなたの考えは何ですか?

この質問は this の重複ではありません。この質問はexternおよびstatic非ローカル変数について尋ねますが、他の質問はファイルスコープとブロックスコープについてですstatic変数。

8
xiver77

適切に設計されたCプログラムでは、ファイル静的変数はクラスのプライベート静的メンバーに似ています。

  • これは、プライベート静的メンバー変数が、それが定義されているクラスの関数によってのみアクセスできる方法と同様に、そのファイル内の関数によってのみアクセスできます。

  • 変数のコピーは1つだけです。

  • その寿命はプログラムの寿命です。

extern変数は、それらをサポートする他の言語と同様に、真のグローバル変数になります。

static非グローバル変数はnotはグローバルと同じくらい悪いです。実際には、それらが必要な場合もあります。

  • アクセスは、作成した関数を介して制御されます。これは、境界チェックとスレッドセーフの両方を含むデータの整合性に役立ちます。 (注:これは保証スレッドセーフではありません。これは、途中で役立つ1つのツールにすぎません)

  • データはカプセル化されています。そのファイルだけがアクセスできます。これは、複数の関数が静的変数にアクセスできるCがカプセル化に近づくのと同じくらい近いものです。

グローバル変数は何があっても悪いです。静的ファイル変数にはプライベート静的変数の利点がありますが、グローバル変数の欠点はありません。

唯一の問題は、C++のように真のプライベート静的変数とは異なり、他のファイルは宣言と一致するextern変数を宣言でき、アクセスを防ぐことはできません。言い換えれば、それをグローバル変数に変換することを避けるために、名誉システムに依存しています。

17
user22815

ファイルスコープまたは関数内のextern変数と非conststatic変数を含むグローバル状態は、特定の問題の簡単な解決策になることがよくありますが、3つの問題があります。

  1. static変数は置換できない依存関係になる傾向があるため、staticはコードをnestableにします。または、よりOOP-yの言葉で言えば、依存関係の逆転の原則に従っていません。 Perlなどの動的言語からCおよびC++に来たので、私のコストモデルは仮想ディスパッチや関数ポインターなどに傾いています。現在の言語では、テスト容易性と優れたアーキテクチャーの間にいくつかの矛盾がありますが、依存関係を明示的にしてテストでそれらをオーバーライドできるようにするマイナーな迷惑は、テストを簡単に記述できることによって著しく相殺され、ソフトウェアが次のように機能していることを確認します期待された。コードをより動的にすることなく、テストの依存関係を挿入するために利用できる唯一のメカニズムは、条件付きコンパイルです。

  2. グローバルな状態はそれを正しさについて推論するのは難しいであり、それはバグにつながります。変数にアクセスできるビットやピースが多くなり、変数を変更できるほど、何が起こっているのか見失いやすくなります。代わりに、変数の単一割り当てを優先してください!どこでもconstを使用してください。正当性チェックを導入できるゲッターとセッターを介して変数を保護することをお勧めします。状態がstaticではなくexternである限り、正確性を維持することは引き続き可能ですが、常に一週間の私が今の私ほど賢くないと思った方がいい。特にC++では、クラスを使用して、何かを誤用することを不可能にするさまざまな抽象化をモデル化できます。そのため、インテリジェンスではなく型システムを利用するようにしてください。

  3. グローバル状態は、関数が再入不可であること、または一度に1つのコンテキストでのみ使用できることを意味している可能性があります。 1つの接続しか管理できないデータベースドライバーを想像してみてください。それは完全に不必要な制限です。実際には、結果の集計に使用されるグローバル変数など、制限はしばしば微妙です。代わりに、データフローを明示的にして、すべてを関数パラメーターを介して渡します。繰り返しになりますが、C++クラスはこれをより扱いやすくすることができます。

明らかに、static const NAMED_CONSTANTS 大丈夫です。関数内でstaticを使用するのは非常に注意が必要です。遅延して初期化された定数には役立ちますが、テストできない場合があります。妥協案は、静的変数から初期値の計算を分離して、両方の部分を個別にテストできるようにすることです。

小さな自己完結型プログラムでは、これらすべてが問題になることはなく、static状態を使い続けることができます。しかし、500 LOCを通過するか、再利用可能なライブラリを作成している場合は、不必要な制限なしに、優れたアーキテクチャと優れたインターフェースについて考え始める必要があります。

6
amon

私はファイルスコープの変数をグローバル変数ほど悪いとは考えていません。結局のところ、これらの変数へのアクセスはすべて1つのソースファイルに限定されます。その制限により、ファイルスコープ変数はC++のプライベート静的データメンバーとほぼ同じか、または悪いものであり、それらの使用を禁止しませんか。

それはすべて、私の見解では、変数のスコープ(定数ではなく、変更可能なもの)に関連付けられています。確かに多少のニュアンスが欠けている見方ですが、これは実用的なカウンターであり、「これは絶対に悪いです!」その後、競合状態など、彼らが批判するものに関連する問題と同様の問題でトリップします。

すべての種類の変数が先頭で宣言され、gotoステートメントがすべての場所を移動する50,000行の関数があるとします。これはこのような巨大な変数スコープではあまり快適ではなく、関数について推論すること、およびそのような変数で何が行われているのかを判断することは非常に困難になります。そのような巨大なケースでは、「外部」と「内部」の副作用の通常の区別は、その実用的な目的の多くを失います。

80行の単純なプログラムを1回だけ作成し、グローバル変数(内部リンケージとファイルスコープまたは外部リンケージのいずれかを使用して、ただしプログラムが非常に小さい)でクランクアウトするとします。それはそれほど悪くはありません。

実装のための数千行のコードを含むプログラム全体のロジックを含むオブジェクト指向言語の巨大なクラスがあるとします。その場合、そのメンバー変数は、上記の80行のプログラムのグローバル変数よりも問題があります。

コード、スレッドの安全性(またはコードの欠如)をより適切かつ確実に推論できるようにしたい場合は、コードをより予測可能にし、あらゆる種類の潜在的なEdgeケースを見逃さずにテストが適切にカバーされるようにします。 、それから変数へのアクセスを狭めるのに役立ちます。

ファイルスコープの静的スコープは、外部リンケージのスコープよりもスコープが狭い傾向がありますが、ソースファイルが100,000行のコードである場合でも、かなり幅広です。したがって、ファイルスコープの静的については、それらを回避できない場合は、それらにアクセスできるソースファイルを膨大なものにしないようにして、スコープを狭くするようにします。その場合、スコープを縮小すると、サイズとデザインスコープが縮小されます。関数(パラメーターを含むローカル変数の場合)、クラス(メンバー変数の場合)、またはモジュール(外部リンケージのあるグローバルの場合、モジュール内でのみアクセス可能)とは対照的に、ソースファイルのソフトウェア全体にアクセス可能な外部リンケージ)。

1
Dragon Energy