Arduinoのグローバル変数は悪ですか?


24

私はプログラミングが比較的新しいので、私が読んでいるコーディングのベストプラクティスの多くは、グローバル変数を使用する正当な理由はほとんどないと述べています(または、最高のコードにはグローバルがまったくありません)。

SDカードを使ってArduinoインターフェースを作成し、コンピューターと会話してモーターコントローラーを実行するソフトウェアを作成するときは、これを念頭に置いて最善を尽くしました。

現在、約1100行の「初心者レベル」コード(複数のアクションを持つ行はありません)に対して46個のグローバルがあります。これは良い比率ですか、それとももっと減らすことを検討すべきですか?また、グローバルの数をさらに減らすためにどのようなプラクティスを採用できますか?

私がここで尋ねているのは、コンピュータープログラミング全般ではなく、Arduino製品のコーディングのベストプラクティスに特に関心があるからです。


2
Arduinoでは、グローバル変数を避けることはできません。関数/メソッドのスコープ外のすべての変数宣言はグローバルです。そのため、関数間で値を共有する必要がある場合、すべての値を引数として渡したい場合を除き、それらはグローバルでなければなりません。

16
@LookAlterno Err、奇妙なマクロとライブラリを備えた単なるC ++であるため、Ardunioでクラスを記述できませんか?その場合、すべての変数がグローバルであるとは限りません。また、Cでさえ、グローバル変数を持つのではなく、関数(おそらく構造体の内部)を関数に渡すことを好むことは通常、ベストプラクティスと見なされます。小規模なプログラムには不便かもしれませんが、通常はプログラムが大きく複雑になるにつれて成果が上がります。
ミューザー

11
@LookAlterno:「避ける」「できない」は非常に異なるものです。
モニカとの軽度のレース

2
実際、一部の組み込みプログラマはローカル変数を禁止し、代わりにグローバル変数(または関数スコープの静的変数)を必要とします。プログラムが小さい場合、単純なステートマシンとして分析しやすくなります。変数が互いに上書きすることは決してないからです(つまり、変数がスタックおよびヒープに割り当てられるため)。
ロブ

1
静的変数とグローバル変数には、コンパイル時のメモリ消費量を把握できるという利点があります。使用可能なメモリが非常に限られているarduinoでは、これが利点となります。初心者が使用可能なメモリを使い果たし、追跡できない障害が発生するのは非常に簡単です。
アンチパターン

回答:


33

それら自体は悪ではありませんが、それらを使用する正当な理由がない場合には使い古される傾向があります。

グローバル変数が有利な場合がたくさんあります。特に、Arduinoのプログラミングは、内部ではPCのプログラミングとは大きく異なります。

グローバル変数の最大の利点は、静的割り当てです。特に、クラスインスタンスなどの大きく複雑な変数の場合。動的割り当て(newなどの使用)は、リソース不足のためにしかめ面にされています。

あなたは、通常のCプログラム(単一で行うようにまた、あなたは、単一のコールツリーを取得しないmain()他の関数を呼び出す機能) -代わりに、あなたは効果的に二つの別々の木(取得setup()、その後、関数を呼び出すloop()時々グローバル変数であることを意味している関数を呼び出す)を、 (あなたは両方で使用する場合、すなわち、あなたの目標を達成するための唯一の方法setup()loop())。

だから、彼らは悪ではなく、ArduinoではPCよりも多くの優れた用途があります。


では、それがloop()(またはで呼び出される複数の関数でloop())使用しているだけの場合はどうでしょうか?最初に定義するよりも異なる方法で設定する方が良いでしょうか?
ATE-ENGE

1
その場合、おそらくloop()でそれらを定義し(おそらく、static繰り返しにわたって値を保持する必要があるかのように)、呼び出しチェーンの関数パラメーターを介してそれらを渡します。
マジェンコ

2
すなわち、一つの方法である、または参照として渡す:void foo(int &var) { var = 4; }foo(n);- n今4である
Majenko

1
クラスはスタックに割り当てることができます。確かに、スタックに大きなインスタンスを配置するのは良くありませんが、それでもです。
JAB

1
@JABスタックを割り当てることができます。
マジェンコ

18

実際のコードを見ずに決定的な答えを出すことは非常に困難です。

グローバル変数は悪ではなく、通常、多くのハードウェアアクセスを行う組み込み環境では意味があります。UARTは4つだけ、I2Cポートは1つだけなどです。したがって、特定のハードウェアリソースに関連付けられた変数にグローバルを使用することは理にかなっています。そして実際、Arduinoのコアライブラリは、その処理を行いますSerialSerial1などグローバル変数です。また、プログラムのグローバル状態を表す変数は通常、グローバルです。

現在、約1100行の[コード]に対して46個のグローバルを持っています。これは良い比率ですか[...]

数字についてではありません。あなた自身に問うべき正しい質問は、これらのグローバルのそれぞれについて、それをグローバルな範囲に含めることが理にかなっているかどうかです。

それでも、46 globalは私には少し高いようです。これらのいくつかが定数値を保持する場合、それらを次のように修飾しconstます。通常、コンパイラはストレージを最適化します。これらの変数のいずれかが単一の関数内でのみ使用される場合は、ローカルにします。関数の呼び出し間で値を保持したい場合は、として修飾しますstatic。クラス内で変数をグループ化し、このクラスのグローバルインスタンスを1つ持つことで、「表示可能な」グローバルの数を減らすこともできます。ただし、そうするのは、物をまとめることが理にかなっている場合に限ってください。GlobalStuffグローバル変数を1つだけにするために大きなクラスを作成しても、コードがわかりやすくなることはありません。


1
OK!私は知りませんでしたstatic私は1つの関数に使用されている変数を持っている場合は、新しい値に関数が(のように呼び出されるたびに取得するvar=millis())私はそれを行う必要がありますかstatic
ATE-ENGE

3
いいえstatic。値を保持する必要がある場合のみです。関数で最初に行うことは、変数の値を現在の時刻に設定する場合、古い値を保持する必要はありません。そのような状況で静的に行われるのは、メモリの無駄遣いです。
アンドリュー

これは非常にバランスの取れた答えであり、グローバルを支持する両方のポイントと、グローバルについての懐疑論を表しています。+1
underscore_d

6

グローバル変数の主な問題は、コードのメンテナンスです。コード行を読み取るとき、パラメーターとして渡された変数またはローカルで宣言された変数の宣言を簡単に見つけることができます。グローバル変数の宣言を見つけるのはそれほど簡単ではありません(多くの場合、IDEが必要です)。

多くのグローバル変数がある場合(40はすでに多くあります)、長すぎない明示的な名前を持つことは困難になります。名前空間の使用は、グローバル変数の役割を明確にする方法です。

Cの名前空間を模倣する貧弱な方法は次のとおりです。

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

IntelまたはARMプロセッサでは、グローバル変数へのアクセスは他の変数よりも遅くなります。おそらく、arduinoの反対です。


2
AVRでは、グローバルはRAM上にありますが、ほとんどの非静的ローカルはCPUレジスタに割り当てられ、アクセスが高速になります。
エドガーボネット

1
問題は実際に宣言を見つけることではありません。コードをすばやく理解できることは重要ですが、最終的にはコードの機能に次ぐものです-そして、実際の問題は、コードの未知の部分でどの宣言がグローバル宣言を使用してあなたが除外したオブジェクトで奇妙なことをすることができるかを見つけることです誰でも自由にやりたいことができるようになります。
underscore_d

5

PC用のプログラミングには使用しませんが、Arduinoにはいくつかの利点があります。すでに言われている場合、ほとんど:

  • 動的メモリ使用量なし(Arduinoの限られたヒープスペースにギャップを作成)
  • どこでも利用できるため、引数として渡す必要はありません(スタックスペースがかかります)

また、場合によっては、特にパフォーマンス面では、必要なときに要素を作成する代わりに、グローバル変数を使用するとよい場合があります。

  • ヒープスペースのギャップを減らすには
  • メモリの動的な割り当ておよび/または解放にはかなりの時間がかかる可能性があります
  • 変数は、複数の理由で使用されるグローバルリストの要素のリストのように「再利用」できます。たとえば、SDのバッファとして1回、後で一時的な文字列バッファとして。

5

すべてのもの(真に悪であるゴトを除く)と同様に、グローバルにはその場所があります。

たとえば、デバッグシリアルポートまたはログファイルがあり、どこからでも書き込みできるようにする必要がある場合、多くの場合、グローバルにすることが理にかなっています。同様に、システムステータス情報の重要な部分がある場合は、多くの場合、それをグローバルにすることが最も簡単なソリューションです。プログラム内のすべての関数に渡さなければならない値を持つことに意味はありません。

他の人が言ったように、46行はコードの1000行を超えるだけでたくさんあるように見えますが、あなたがしていることの詳細を知らずに、それらを使いすぎているかどうかを言うのは難しいです。

ただし、すべてのグローバルについて、いくつかの重要な質問を自問してください。

名前が明確で具体的であるため、別の場所で誤って同じ名前を使用しようとしないでください。名前を変更しない場合。

これを変更する必要はありますか?そうでない場合は、constにすることを検討してください。

これはどこでも見える必要がありますか、または関数呼び出し間で値が維持されるようにグローバルのみですか?関数に対してローカルにし、キーワードstaticを使用することを検討してください。

私が注意していないときにコードの一部によってこれが変更された場合、物事は本当にひどく失敗しますか?たとえば、名前とID番号など、グローバルな2つの関連変数がある場合(ほぼすべての場所で情報が必要な場合のグローバルの使用に関する前述のメモを参照)、それらのいずれかが変更された場合、他の厄介なことは発生しません。はい、あなたはちょうど注意することができますが、時には注意を少し強要することは良いことです。たとえば、それらを別の.cファイルに入れてから、両方を同時に設定し、それらを読み取ることができる関数を定義します。次に、関連付けられたヘッダーファイルに関数のみを含めます。これにより、残りのコードは定義された関数を介してのみ変数にアクセスできるため、混乱することはありません。

-更新-一般的なコーディングではなく、Arduino固有のベストプラクティスについて質問したことがわかりましたが、これは一般的なコーディングの回答です。しかし、正直なところ大きな違いはありません。良い習慣は良い習慣です。startup()そしてloop()、あなたが使用していることをArduinoの手段の構造いくつかの状況で、もう少し他のプラットフォームよりもグローバルしかし、それは本当に多くを変更しない、あなたはいつもあなたがどんなプラットフォームの制限の範囲内で行うことができます最善を目指して終わりますプラットフォームは。


Arduinosについては何も知りませんが、デスクトップとサーバーの開発はたくさんしています。gotosには1つの許容可能な使用法(IMHO)があり、それはネストされたループから抜け出すことです。
永続性

gotoが悪だと思われる場合は、Linuxコードを確認してください。間違いなく、それらはtry...catchブロックとして使用される場合、ブロックと同じくらい悪です。
ドミトリーグリゴリエフ

5

彼らは悪ですか?多分。グローバルの問題は、制限なしで、実行中の関数またはコードによって、いつでもアクセスおよび変更できることです。これは、たとえば、さかのぼって説明するのが難しい状況につながる可能性があります。したがって、可能であれば、グローバルの量を最小限に抑えて、量をゼロに戻すことが望ましいです。

彼らは避けることができますか?ほとんど常にはい。Arduinoの問題は、彼らがあなたsetup()とあなたを仮定するこの2つの機能アプローチにあなたを強制することですloop()。この特定のケースでは、これら2つの関数(おそらくmain())の呼び出し元関数のスコープにアクセスできません。持っていれば、すべてのグローバル変数を削除して、代わりにローカル変数を使用できます。

以下を想像してください:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

これはおそらく、Arduinoプログラムの主な機能が多少なりとも似ていることでしょう。あなたは、両方に必要な変数setup()loop()関数は、好ましくはの範囲内で宣言されるだろうmain()機能ではなく、グローバルスコープ。次に、それらを引数として渡すことにより(必要に応じてポインターを使用して)、他の2つの関数からアクセス可能にすることができます。

例えば:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

この場合、両方の関数の署名も変更する必要があることに注意してください。

これは現実的でも望ましくもないかもしれないので、Arduinoプログラムからほとんどのグローバルを強制的にプログラム構造を変更せずに削除する方法は実際には1つしかないと思います。

私が正しく思い出すと、Arduinoのプログラミング中にCではなくC ++を完全に使用できます。OOP(Object Oriented Programming)またはC ++に(まだ)慣れていない場合は、ある程度慣れる必要があります。読書。

私の提案は、プログラムクラスを作成し、このクラスの単一のグローバルインスタンスを作成することです。クラスはオブジェクトの青写真と見なされるべきです。

次のプログラム例を考えてみましょう。

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà、私たちはほぼすべてのグローバルを排除しました。アプリケーションロジックの追加を開始する関数はProgram::setup()and Program::loop()関数です。これらの機能は、インスタンスの特定のメンバ変数へのアクセス権を持っているmyFirstSampleVariableし、mySecondSampleVariable伝統的なのに対し、setup()およびloop()機能これらの変数は、クラスがプライベートマークされているとしてアクセスすることはできません。この概念は、データのカプセル化またはデータ隠蔽と呼ばれます

OOPやC ++を教えることは、この質問に対する答えの範囲外であるため、ここで停止します。

要約すると、グローバルは回避する必要があり、グローバルの量を大幅に削減することはほぼ常に可能です。また、Arduino用にプログラミングしているとき。

最も重要なことは、私の答えがあなたにとっていくらか役に立つことを願っています:)


必要に応じて、スケッチで独自のmain()を定義できます。これは何であるかのような株式1ルックス:github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/...
per1234

シングルトンは事実上グローバルであり、混乱を招くような方法で着飾っているだけです。同じ欠点もあります。
patstew

@patstew同じ欠点があると感じていることを説明してもらえますか?私の意見では、あなたがあなたの利益のためにデータカプセル化を使用することができるので、それはそうではありません。
-Arjen

@ per1234ありがとう!私は間違いなくArduinoの専門家ではありませんが、最初の提案がその時点でもうまくいくと思います。
-Arjen

2
まあ、それはまだプログラム内のどこからでもアクセスできるグローバルな状態であり、のProgram::instance().setup()代わりにアクセスするだけですglobalProgram.setup()。関連するグローバル変数を1つのクラス/構造体/名前空間に入れることは、特にそれらが関連する関数のいくつかでのみ必要な場合に有益ですが、シングルトンパターンは何も追加しません。つまりstatic Program p;、グローバルストレージとstatic Program& instance()グローバルアクセスがあり、これは単にと同じですProgram globalProgram;
patstew

4

グローバル変数は決して悪ではありません。彼らに対する一般的なルールは、より良い決断を下すための経験を得るのに十分長く生き残るための松葉杖です。

グローバル変数とは、あるものが1つしかないという固有の仮定です(複数のものを含む可能性があるグローバル配列またはマップについて話している場合でも、そのようなリストまたはマッピングの1つであり、複数の独立したリストまたはマッピングではありません)。

グローバルを使用する前に、自分自身に問いかけたいと思います。これを複数使用したいと考えることは考えられますか?それが本当であることが判明した場合、そのことをグローバル化しないようにコードを変更する必要があり、コードの他の部分が一意性の仮定に依存している方法に沿っておそらく見つけるでしょうそれらも修正する必要があり、プロセスは退屈でエラーが発生しやすくなります。「グローバルを使用しない」が教えられるのは、通常、最初からグローバルを回避するのは非常に小さなコストであり、後で大きなコストを支払う可能性を回避するためです。

しかし、グローバルが許可する単純化された前提は、コードをより小さく、より速く、より少ないメモリを使用します。組み込みでは、PCよりもコードサイズやCPU時間やメモリの制約を受ける可能性が高いため、これらの節約は重要です。要件に、より剛性を持ち、多くの組み込み用途にも-あなたが知っているあなたのチップが特定の周辺機器の一つを持っていることを、ユーザーは単にUSBポートまたは何かに別のものをプラグインすることはできません。

ユニークと思われるものを複数必要とするもう1つの一般的な理由は、テストです。2つのコンポーネント間の相互作用のテストは、コンポーネントのテストインスタンスを関数に渡すだけで簡単ですが、グローバルコンポーネントの動作を変更しようとするとトリッキーな提案。しかし、組み込みの世界でのテストは他の場所とは非常に異なる傾向があるため、これはあなたには当てはまらないかもしれません。私の知る限り、Arduinoにはテスト文化はまったくありません。

したがって、価値があると思われる場合はグローバルを使用してください。コード警察は来てあなたをつかまえません。間違った選択はあなたのためにさらに多くの仕事につながる可能性があるので、あなたがわからない場合...


0

Arduinoのグローバル変数は悪ですか?

グローバル変数を含め、本質的に悪ではありません。私はそれを「必要な悪」と特徴づけます-それはあなたの人生をずっと楽にすることができますが、注意して接近するべきです。

また、グローバルの数をさらに減らすためにどのようなプラクティスを採用できますか?

ラッパー関数を使用して、グローバル変数にアクセスします。少なくともスコープの観点から管理しています。


3
ラッパー関数を使用してグローバル変数にアクセスする場合、これらの関数内に変数を配置することもできます。
ドミトリーグリゴリエフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.