組み込みシステムでのグローバル変数の使用


17

私は自分の製品のファームウェアを書き始め、私はここで新人です。グローバル変数またはグローバル関数を使用しないことに関する多くの記事を読みました。8ビットシステムでグローバル変数を使用するための制限はありますか、それとも完全な「No-No」ですか。システムでグローバル変数を使用する方法、またはそれらを完全に回避する必要がありますか?

私のファームウェアをよりコンパクトにするために、このトピックに関して皆さんから貴重なアドバイスを受けたいと思います。


この質問は、組み込みシステムに固有のものではありません。重複はここで見つけることができます
ランディン

@Lundinリンクから:「メモリが非常に限られている組み込み環境でのみ重要なこれらの日。組み込みが他の環境と同じであり、プログラミング規則が全面的に同じであると仮定する前に知っておくべきこと」
エンドリス

@endolith staticファイルのスコープは「グローバル」と同じではありません。以下の私の答えをご覧ください。
ランディン

回答:


31

@Philのガイドラインに留意する限り、グローバル変数を正常に使用できます。ただし、ここでは、コンパイルされたコードのサイズを小さくせずに問題を回避する優れた方法をいくつか紹介します。

  1. 1つの関数内でのみアクセスする永続状態には、ローカルの静的変数を使用します。

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. 構造体を使用して、関連する変数をまとめて使用し、使用する場所と使用しない場所を明確にします。

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. グローバル静的変数を使用して、現在のCファイル内でのみ変数を表示します。これにより、名前の競合による他のファイルのコードによる偶発的なアクセスを防ぎます。

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

最後の注意として、割り込みルーチン内でグローバル変数を変更し、他の場所でそれを読み取る場合:

  • 変数をマークしますvolatile
  • CPUにとってアトミックであることを確認してください(つまり、8ビットCPUの場合は8ビット)。

または

  • ロック機構を使用して、変数へのアクセスを保護します。

揮発性変数やアトミック変数は、エラーを回避したり、何らかのロック/セマフォを必要としたり、変数への書き込み時に一時的に割り込みをマスクしたりするのに役立ちません。
ジョンU 14年

3
それは「うまく動作する」という非常に狭い定義です。私のポイントは、不安定なものを宣言しても、競合を防ぐことはできないということでした。また、3番目の例は素晴らしいアイデアではありません。同じ名前の 2つの別個のグローバルを持つことは、少なくともコードの理解/保守を難しくします。
ジョンU 14年

1
@JohnU競合状態を防ぐためにvolatileを使用するべきではありませんが、実際には役に立ちません。組み込みシステムコンパイラで一般的な危険なコンパイラ最適化バグを防ぐためにvolatileを使用する必要があります。
ランディン14年

2
@JohnU:volatile変数の通常の使用は、ある実行コンテキストで実行されているコードが別の実行コンテキストのコードに何かが起こったことを知らせることです。8ビットシステムでは、128を超えない2のべき乗のバイト数を保持するバッファーは、1つの揮発性バイトで管理できます。 1つの実行コンテキストのみがデータをバッファーに入れ、1つだけがデータをバッファーから取り出すという条件で、取り出されたライフタイムのバイト数を示す別のパラメーター。
supercat

2
@JohnU:何らかの形式のロックを使用したり、一時的に割り込みを無効にしてバッファを管理したりすることは可能かもしれませんが、実際には必要でも役に立たないでしょう。バッファが128〜255バイトを保持する必要がある場合、コーディングをわずかに変更する必要があり、それ以上を保持する必要がある場合は、割り込みを無効にする必要がありますが、8ビットシステムバッファは小さい傾向があります。より大きなバッファを持つシステムは、一般に8ビットを超えるアトミック書き込みを実行できます。
supercat 14年

24

8ビットシステムでグローバル変数を使用したくない理由は、他のシステムでグローバル変数を使用したくない場合と同じです。プログラムの動作についての推論が難しくなります。

「グローバル変数を使用しない」などのルールに固執するのは、悪いプログラマーだけです。優れたプログラマーは、ルールの背後にある理由を理解し、ルールをガイドラインのように扱います。

あなたのプログラムは理解しやすいですか?その動作は予測可能ですか?他のパーツを壊さずに、その一部を変更するのは簡単ですか?これらの質問のそれぞれに対する答えがyesの場合、あなたは良いプログラムに向かっています。


1
@MichaelKarasが言ったこと-これらのことの意味と、それらが物事にどのように影響するか(そして、どのように噛み付くことができるか)を理解することが重要です。
ジョンU 14年

5

グローバル変数(略して「グローバル」)の使用を完全に避けるべきではありません。しかし、慎重に使用する必要があります。グローバルの過剰使用に伴う実際的な問題:

  • グローバルは、コンパイルユニット全体に表示されます。コンパイルユニットのコードは、グローバルを変更できます。変更の結果は、このグローバルが評価されるあらゆる場所に現れる可能性があります。
  • その結果、グローバル化によりコードが読みにくくなり、理解しにくくなります。プログラマは常に、グローバルが評価および割り当てられるすべての場所に留意する必要があります。
  • グローバルを過度に使用すると、コードの欠陥が生じやすくなります。

g_グローバル変数の名前にプレフィックスを追加することをお勧めします。たとえば、g_iFlags。コード内にプレフィックスが付いた変数を見ると、それがグローバルであることがすぐにわかります。


2
フラググローバルである必要はありません。ISRは、たとえば、静的変数を持つ関数を呼び出すことができます。
フィルフロスト14年

+1そのようなトリックについて聞いたことがありません。その段落を回答から削除しました。static旗はどのように見えるようになりmain()ますか?あなたが持っている同じ関数がstaticそれをmain()後で返すことができることを意味していますか?
ニックアレクセエフ

それがそれをする一つの方法です。おそらく、関数は設定するために新しい状態を取り、古い状態を返します。他にもたくさんの方法があります。フラグを設定する関数と、フラグの状態を保持する静的なグローバル変数を持つフラグを取得する関数を備えたソースファイルがあるとします。技術的にはこれはCの用語では「グローバル」ですが、その範囲はそのファイルに限定されており、知る必要がある機能のみが含まれています。つまり、スコープは「グローバル」ではなく、実際に問題です。C ++は、追加のカプセル化メカニズムを提供します。
フィルフロスト14年

4
グローバルを回避するいくつかの方法(デバイスドライバーを介してのみハードウェアにアクセスするなど)は、リソースが非常に不足している8ビット環境には非効率的です。
スペロペファニー14年

1
@SpehroPefhany 優れたプログラマーはルールの背後にある理由を理解し、ルールをガイドラインのように扱います。ガイドラインに矛盾がある場合、優れたプログラマーはバランスを慎重に評価します。
フィルフロスト14年

4

埋め込み作業でのグローバルデータ構造の利点は、静的であることです。必要なすべての変数がグローバルである場合、関数が入力され、スタック上に関数用のスペースが作成されたときに、誤ってメモリ不足になることはありません。しかし、その時点でなぜ機能があるのでしょうか?GOSUBが許可されていないBASICプログラムのように、すべてのロジックとプロセスを処理する1つの大きな関数ではありません。この考えを十分に理解すれば、1970年代からの典型的なアセンブリ言語プログラムを作成できます。保守およびトラブルシューティングが効率的で不可能です。

したがって、状態変数(たとえば、すべての関数がシステムが解釈または実行状態にあるかどうかを知る必要がある場合)や、多くの関数で見る必要がある他のデータ構造のように、@ PhilFrostが言うように、あなたの機能は予測可能ですか?終わらない入力文字列でスタックを埋める可能性はありますか?これらはアルゴリズム設計の問題です。

staticは関数の内部と外部で異なる意味を持つことに注意してください。/programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c


1
多くの組み込みシステムコンパイラは、自動変数を静的に割り当てますが、同時にスコープに入れられない関数によって使用されるオーバーレイ変数。これにより、一般的に、すべての静的に可能な呼び出しシーケンスが実際に発生する可能性があるスタックベースのシステムの最悪の場合に等しいメモリ使用量が得られます。
supercat

4

グローバル変数は、真のグローバル状態にのみ使用してください。グローバル変数を使用して、たとえば地図の北の境界の緯度などを表す場合、「地図の北の境界」が1つしか存在できない場合にのみ機能します。将来、コードが異なる北の境界を持つ複数のマップで動作する必要がある場合、北の境界にグローバル変数を使用するコードを修正する必要がある可能性があります。

典型的なコンピューターアプリケーションでは、何かが複数あることは決してないと想定する特定の理由はありません。ただし、組み込みシステムでは、そのような仮定ははるかに合理的です。複数の同時ユーザーをサポートするために一般的なコンピュータープログラムが呼び出される可能性がありますが、一般的な組み込みシステムのユーザーインターフェイスは、ボタンとディスプレイを操作する1人のユーザーによる操作用に設計されます。そのため、いつでも単一のユーザーインターフェイス状態になります。複数のユーザーが複数のキーボードやディスプレイを操作できるようにシステムを設計するには、単一のユーザー向けに設計するよりもはるかに複雑で、実装に時間がかかります。システムが複数のユーザーをサポートするために呼び出されない場合、そのような使用を容易にするために投資された余分な労力は無駄になります。マルチユーザーサポートが必要になる可能性が高い場合を除き、マルチユーザーサポートが必要な場合、マルチユーザーの追加に余分な時間を費やすよりも、シングルユーザーインターフェースに使用されるコードを破棄するリスクを負う方が賢明ですおそらく決して必要とされないユーザーサポート。

組み込みシステムに関連する要因は、多くの場合(特にユーザーインターフェイスを含む)、複数のものをサポートする唯一の実用的な方法は、複数のスレッドを使用することです。マルチスレッドの必要性が他にない場合、マルチスレッドを使用してシステムの複雑さを増すよりも、単純にシングルスレッドの設計を使用するほうがよいでしょう。とにかく複数の何かを追加するのに巨大なシステムの再設計が必要な場合、いくつかのグローバル変数の使用をやり直す必要があるかどうかは関係ありません。


したがって、互いに衝突しないグローバル変数を保持することは問題になりません。例えば:カウント日や週のDay_cntr、week_cntrなどは、私は1つが意図的に同じ目的に似ていると明らかに圧倒的な応答をthose.Thanksをたくさん定義する必要があり、グローバル変数の多くを使用してはならない信頼respectively.And :)。
Rookie91

-1

多くの人々がこの主題について混乱しています。グローバル変数の定義は次のとおりです。

プログラムのどこからでもアクセスできるもの。

これは、キーワードで宣言されているファイルスコープ変数とは異なりますstatic。これらはグローバル変数ではなく、ローカルのプライベート変数です。

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

グローバル変数を使用する必要がありますか?それがうまくいくいくつかのケースがあります:

それ以外の場合は、グローバル変数を使用しないでください。そうする理由はありません。代わりに、ファイルスコープ変数を使用します。これはまったく問題あり。

特定のタスクを実行するように設計された独立した自律的なコードモジュールの作成に努める必要があります。これらのモジュール内では、内部ファイルスコープ変数はプライベートデータメンバーとして存在する必要があります。この設計方法はオブジェクト指向として知られており、優れた設計として広く認知されています。


なぜ下票なのか?
m。アリン14年

2
私が考える「グローバル変数」は、グローバルセグメント(テキストではなく、スタック、またはヒープ)への割り当てを記述するために使用することができます。その意味で、ファイルの静的変数と関数の静的変数は「グローバル」です。この質問の文脈では、グローバルが割り当てセグメントではなくスコープを参照していることはいくらか明らかです(ただし、OPはこれを知らなかった可能性があります)。
ポールA.クレイトン

1
@ PaulA.Clayton「グローバルメモリセグメント」と呼ばれる正式な用語を聞いたことがない。あなたの変数は、次のいずれかの場所になってしまいます:レジスタスタック(ランタイムの割り当て)、ヒープ(実行時の割り当て)、.dataのセグメント(明示的に初期化静的格納変数)、.bssのセグメント(ゼロ化された静的格納変数)、.rodataの(読み取りを-定数のみ)または.text(コードの一部)。それらが他のどこかで終わる場合、それはプロジェクト固有の設定です。
ランディン

1
@ PaulA.Clayton「グローバルセグメント」と呼ぶものがセグメントだと思い.dataます。
ランディン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.