定数に#defineまたはconst intを使用する方が良いですか?


26

Arduinoは奇妙なハイブリッドであり、組み込みの世界(従来はC環境)でC ++機能が使用されます。実際、多くのArduinoコードは非常にCに似ています。

Cは伝統的#defineに定数にsを使用していました。これにはいくつかの理由があります。

  1. を使用して配列サイズを設定することはできませんconst int
  2. const intcaseステートメントラベルとしては使用できません(ただし、これは一部のコンパイラで機能します)
  3. constを別ので初期化することはできませんconst

より多くの理由については、StackOverflowでこの質問を確認できます。

それでは、Arduinoには何を使用すべきでしょうか?私は傾向がありますが#define、一部のコードconstはブレンドを使用しており、一部のコードはブレンドを使用しています。


優れたオプティマイザーはそれを無効にします
ラチェットフリーク14

3
本当に?型の安全性などをコンパイラがどのように解決しようとしているかがわかりません。配列の長さなどを定義するためにを使用することはできません。
サイバーギボン14

同意する。加えて、以下の私の答えを見れば、どのタイプを使うべきか本当にわからない状況#defineがあるので、当然の選択です。私の例は、A5のようなアナログピンの命名です。aとして使用できる適切なタイプはないconstため、唯一の選択肢は、aを使用し#define、意味を解釈する前にテキスト入力としてコンパイラに代入させることです。
SDsolar

回答:


21

CとC ++で同じように動作const intないことに注意することが重要です。したがって、実際には、元の質問とPeter Bloomfieldsの広範な答えで暗示されている反対意見のいくつかは無効です。

  • C ++では、const int定数はコンパイル時の値であり、ケースラベルなどとして配列の制限を設定するために使用できます。
  • const int定数は、必ずしもストレージを占有するわけではありません。アドレスを取得するかexternを宣言しない限り、通常はコンパイル時に存在します。

ただし、整数定数の場合、(名前付きまたは匿名)を使用することが望ましい場合がありますenum。私はしばしばこれが好きです:

  • Cとの後方互換性があります。
  • タイプセーフとほぼ同じですconst int(C ++ 11のタイプセーフと同等)。
  • 関連する定数をグループ化する自然な方法を提供します。
  • ある程度の名前空間の制御にも使用できます。

したがって、慣用的なC ++プログラムでは#define、整数定数を定義するために使用する理由はまったくありません。C互換性を維持したい場合(技術的な要件のため、古い学校を始めている、または同僚がそのように好むため)、使用するのenumではなく、使用する必要があります#define


2
いくつかの優れた点を挙げます(特に配列の制限について-Arduino IDEを備えた標準コンパイラがまだサポートしていないことに気付きませんでした)。コンパイル時の定数はストレージを使用しないと言うのはまったく正しくありません。その値は、使用する場所のどこでもコード(SRAMではなくプログラムメモリ)で発生する必要があるためです。つまり、ポインターよりも多くのスペースを使用するタイプのフラッシュに影響を与えます。
ピーターブルームフィールド14

1
「実際、元の質問で示唆されている反対意見のいくつか」-元の質問でなぜそれらがCの制約であると述べられているように有効ではないのですか?
サイバーギボン14

@Cyber​​gibbons ArduinoはC ++に基づいているため、Cのみの制約が適切である理由は明らかではありません(何らかの理由でコードがCと互換性がある必要がない限り)。
マイクロテリオン14年

3
@ PeterR.Bloomfield、追加のストレージを必要としない定数に関する私のポイントはに限定されていましたconst int。より複雑なタイプの場合、ストレージが割り当てられることは正しいですが、たとえそうであっても、の場合よりも悪化することはほとんどありません#define
マイクロテリオン14年

7

編集:マイクロテリオンはここで私のポイントのいくつか、特にメモリ使用量を修正する優れた答えを与えます。


確認した#defineように、コンパイラーはconst変数を許可しないため、の使用を強制される特定の状況があります。同様に、状況によっては、値の配列が必要な場合(つまり、配列を持つことができない場合など)に変数の使用を余儀なくされます#define

ただし、単一の「正しい」答えが必ずしもあるとは限らない他の多くの状況があります。ここに私が従ういくつかのガイドラインがあります:

型の安全性
一般的なプログラミングの観点から、const変数は通常望ましい(可能な場合)。その主な理由は型安全です。

#define(プリプロセッサマクロ)に直接コピーリテラルコードの各位置への値、すべての利用独立を行います。これは、タイプが使用方法/場所に応じて異なる方法で解決される可能性があるため、仮説的に曖昧になる可能性があります。

const変数はその宣言によって決定され、初期化時に解決されるしか一種です。異なる振る舞いをする前に、明示的なキャストが必要になることがよくあります(暗黙的にタイププロモーションを安全に行えるさまざまな状況がありますが)。少なくとも、型の問題が発生した場合、コンパイラーは(正しく構成されていれば)より信頼性の高い警告を出すことができます。

これの可能な回避策は、内に明示的なキャストまたはタイプサフィックスを含めること#defineです。例えば:

#define THE_ANSWER (int8_t)42
#define NOT_QUITE_PI 3.14f

ただし、その方法は、使用方法によっては、場合によっては構文の問題を引き起こす可能性があります。

メモリ使用量
汎用コンピューティングとは異なり、Arduinoのようなものを扱う場合、メモリは明らかに貴重です。const変数とa を使用すると#define、データがメモリのどこに保存されるかに影響を与える可能性があり、どちらか一方を使用せざるを得ない場合があります。

  • const 変数は(通常)他のすべての変数とともにSRAMに保存されます。
  • で使用されるリテラル値は、#define多くの場合、スケッチ自体とともにプログラム空間(フラッシュメモリ)に保存されます。

(コンパイラの構成や最適化など、何かが正確に保存される方法と場所に影響を与える可能性のあるさまざまなものがあることに注意してください。)

SRAMとフラッシュには異なる制限があります(たとえば、Unoの場合はそれぞれ2 KBと32 KB)。一部のアプリケーションでは、SRAMを使い果たすのが非常に簡単なので、いくつかのことをフラッシュに移行すると役立つ場合があります。おそらく一般的ではありませんが、逆も可能です。

PROGMEM
データをプログラム空間(フラッシュ)に保存しながら、タイプセーフの利点を得ることができます。これは、PROGMEMキーワードを使用して行われます。すべての型で機能するわけではありませんが、一般的に整数または文字列の配列に使用されます。

ドキュメントに記載されている一般的な形式は次のとおりです。

dataType variableName[] PROGMEM = {dataInt0, dataInt1, dataInt3...}; 

文字列テーブルはもう少し複雑ですが、ドキュメントには詳細があります。


1

実行中に変更されない指定されたタイプの変数の場合、通常はどちらも使用できます。

変数に含まれるデジタルピン番号の場合、次のように機能します。

const int ledPin = 13;

しかし、私がいつも使用する状況が1つあります #define

アナログピン番号は英数字であるため、定義することです。

もちろん、プログラム全体でピン番号を、などのようa2にハードコードできa3、コンパイラはそれらをどう処理するかを知っています。その後、ピンを変更する場合は、使用するたびに変更する必要があります。

さらに、ピン定義を常に1か所で最上部に配置するのが好きなので、constとして定義されているピンにどのタイプが適切かが問題になりますA5

それらの場合、私は常に使用します #define

分圧器の例:

//
//  read12     Reads Voltage of 12V Battery
//
//        SDsolar      8/8/18
//
#define adcInput A5    // Voltage divider output comes in on Analog A5
float R1 = 120000.0;   // R1 for voltage divider input from external 0-15V
float R2 =  20000.0;   // R2 for voltage divider output to ADC
float vRef = 4.8;      // 9V on Vcc goes through the regulator
float vTmp, vIn;
int value;
.
.
void setup() {
.
// allow ADC to stabilize
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin);
.
void loop () {
.
.
  value=analogRead(adcPin);
  vTmp = value * ( vRef / 1024.0 );  
  vIn = vTmp / (R2/(R1+R2)); 
 .
 .

すべてのセットアップ変数は最上部にありadcPin、コンパイル時を除いての値が変更されることはありません。

タイプadcPinが何であるかについて心配する必要はありません。また、バイナリを格納するために定数を保存するために余分なRAMは使用されません。

コンパイラadcPinは、A5コンパイルする前にの各インスタンスを文字列に単純に置き換えます。


決定する他の方法を説明する興味深いArduinoフォーラムスレッドがあります。

#define vs. const変数 (Arduinoフォーラム)

Excertps:

コード置換:

#define FOREVER for( ; ; )

FOREVER
 {
 if (serial.available() > 0)
   ...
 }

デバッグコード:

#ifdef DEBUG
 #define DEBUG_PRINT(x) Serial.println(x)
#else
 #define DEBUG_PRINT(x)
#endif

定義truefalseブールとしてRAMを保存します

Instead of using `const bool true = 1;` and same for `false`

#define true (boolean)1
#define false (boolean)0

その多くは個人的な好みに帰着しますが、#defineより多用途であることは明らかです。


同じ状況で、a constはa より多くのRAMを使用しません#define。アナログピンについては、違いはありませんがconst uint8_t、それらをとして定義しますconst int
エドガーボネット

a constは実際に使用されるまでRAMを実際に使用しない[...]」と書きました。あなたは私のポイントを逃しました:ほとんどの場合、a constはRAM が使用されていても使用されません。次に、「これはマルチパスコンパイラです」。最も重要なのは、最適化コンパイラです。可能な限り、定数は即値オペランドに最適化されます
エドガーボネット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.