Ist es besser, #define oder const int für Konstanten zu verwenden?

26

Arduino ist ein seltsamer Hybrid, bei dem einige C ++ - Funktionen in der eingebetteten Welt verwendet werden - traditionell eine C-Umgebung. Tatsächlich ist eine Menge Arduino-Code jedoch sehr C-artig.

C hat traditionell #defines für Konstanten verwendet. Dafür gibt es eine Reihe von Gründen:

  1. Sie können mit keine Array-Größen einstellen const int.
  2. Sie können keine const intcase-Anweisungsbezeichnungen verwenden (obwohl dies in einigen Compilern funktioniert).
  3. Sie können nicht constmit einem anderen initialisieren const.

Sie können diese Frage auf StackOverflow überprüfen, um weitere Überlegungen anzustellen.

Also, was sollen wir für Arduino verwenden? Ich tendiere dazu #define, aber ich sehe, dass einige Code verwenden constund andere eine Mischung.

Cybergibbons
quelle
Ein guter Optimierer macht es unsicher
Ratschenfreak
3
"Ja wirklich?" Ich sehe nicht ein, wie ein Compiler Dinge wie Typensicherheit lösen wird, nicht in der Lage zu sein, die Länge eines Arrays zu definieren und so weiter.
Cybergibbons
Genau. Wenn Sie sich meine Antwort unten ansehen, dann zeige ich, dass es Umstände gibt, in denen Sie nicht wirklich wissen, welchen Typ Sie verwenden sollen. Dies #defineist die naheliegende Wahl. Mein Beispiel ist die Benennung von analogen Pins - wie A5. Es gibt keinen geeigneten Typ dafür, der als verwendet werden könnte. constDie einzige Möglichkeit besteht darin, a zu verwenden #defineund ihn vom Compiler als Texteingabe ersetzen zu lassen, bevor die Bedeutung interpretiert wird.
SDsolar

Antworten:

21

Es ist wichtig zu beachten , dass const intsich nicht gleich in C verhalten und in C ++, so in der Tat einige der Einwände dagegen, die in der ursprünglichen Frage und in Peter Bloomfields umfangreiche Antwort erwähnt worden sind , sind nicht gültig:

  • In C ++ sind const intKonstanten Werte für die Kompilierungszeit und können zum Festlegen von Array-Grenzen, als Fallbezeichnungen usw. verwendet werden.
  • const intKonstanten belegen nicht unbedingt Speicherplatz. Sofern Sie ihre Adresse nicht angeben oder extern deklarieren, haben sie in der Regel nur eine Kompilierzeit.

Bei ganzzahligen Konstanten ist es jedoch häufig vorzuziehen, einen (benannten oder anonymen) Wert zu verwenden enum. Ich mag das oft, weil:

  • Es ist abwärtskompatibel mit C.
  • Es ist fast so typsicher wie const int(genauso wie typsicher in C ++ 11).
  • Es bietet eine natürliche Möglichkeit, verwandte Konstanten zu gruppieren.
  • Sie können sie sogar für eine gewisse Namespace-Steuerung verwenden.

In einem idiomatischen C ++ - Programm gibt es also keinen Grund #define, eine Ganzzahlkonstante zu definieren. Auch wenn Sie C-kompatibel bleiben möchten (aufgrund technischer Anforderungen, weil Sie in die Jahre gekommen sind oder weil Leute, mit denen Sie zusammenarbeiten, dies vorziehen), können enumund sollten Sie dies weiterhin tun, anstatt es zu verwenden #define.

Mikrotherion
quelle
2
Sie sprechen einige hervorragende Punkte an (insbesondere in Bezug auf die Array-Grenzen - ich hatte noch nicht erkannt, dass der Standard-Compiler mit Arduino IDE dies unterstützt). Es ist nicht ganz richtig zu sagen, dass eine Konstante zur Kompilierungszeit keinen Speicherplatz belegt, da ihr Wert im Code (dh im Programmspeicher anstatt im SRAM) an einer beliebigen Stelle, an der sie verwendet wird, immer noch vorkommen muss. Das heißt, es wirkt sich auf den verfügbaren Flash-Speicher für alle Typen aus, die mehr Platz als ein Zeiger benötigen.
Peter Bloomfield
1
"Tatsächlich sind also einige der Einwände dagegen, die in der ursprünglichen Frage angedeutet wurden" - warum sind sie in der ursprünglichen Frage nicht gültig, da es heißt, dass dies Einschränkungen von C sind?
Cybergibbons
@ Cybergibbons Arduino basiert auf C ++, daher ist mir nicht klar, warum nur C-Einschränkungen relevant sind (es sei denn, Ihr Code muss aus irgendeinem Grund auch mit C kompatibel sein).
Microtherion
3
@ PeterR.Bloomfield, mein Punkt zu Konstanten, die keinen zusätzlichen Speicher benötigen, war beschränkt auf const int. Bei komplexeren Typen haben Sie Recht, dass Speicher zugewiesen wird, aber es ist unwahrscheinlich, dass Sie schlechter dran sind als bei einem #define.
Microtherion
7

BEARBEITEN: microtherion gibt eine hervorragende Antwort, die einige meiner Punkte hier korrigiert, insbesondere in Bezug auf die Speichernutzung.


Wie Sie festgestellt haben, gibt es bestimmte Situationen, in denen Sie zur Verwendung von a gezwungen sind #define, da der Compiler keine constVariablen zulässt . In einigen Situationen sind Sie gezwungen, Variablen zu verwenden, z. B. wenn Sie ein Array von Werten benötigen (dh Sie können kein Array von Werten haben #define).

Es gibt jedoch viele andere Situationen, in denen nicht unbedingt eine einzige „richtige“ Antwort vorliegt. Hier sind einige Richtlinien, denen ich folgen würde:

Typensicherheit Im
Hinblick auf die allgemeine Programmierung constsind Variablen in der Regel vorzuziehen (soweit möglich). Der Hauptgrund dafür ist die Typensicherheit.

Ein #define(Präprozessor-Makro) kopiert den Literalwert direkt an jede Stelle im Code, wodurch jede Verwendung unabhängig wird. Dies kann hypothetisch zu Mehrdeutigkeiten führen, da der Typ je nach Verwendungszweck unterschiedlich aufgelöst werden kann.

Eine constVariable ist immer nur ein Typ, der durch ihre Deklaration bestimmt und bei der Initialisierung aufgelöst wird. Es wird oft eine explizite Besetzung erfordern, bevor es sich anders verhält (obwohl es verschiedene Situationen gibt, in denen es sicher implizit vom Typ heraufgestuft werden kann). Zumindest kann der Compiler (bei korrekter Konfiguration) eine zuverlässigere Warnung ausgeben, wenn ein Typproblem auftritt.

Eine mögliche Problemumgehung besteht darin, eine explizite Umwandlung oder ein Typ-Suffix in ein einzuschließen #define. Beispielsweise:

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

Dieser Ansatz kann jedoch in einigen Fällen möglicherweise zu Syntaxproblemen führen, je nachdem, wie er verwendet wird.

Speichernutzung
Im Gegensatz zum Allzweck-Computing hat Speicher offensichtlich einen hohen Stellenwert im Umgang mit so etwas wie einem Arduino. Die Verwendung einer constVariablen im Vergleich zu einer #definekann sich darauf auswirken, wo die Daten im Speicher gespeichert sind, wodurch Sie möglicherweise gezwungen werden, die eine oder andere zu verwenden.

  • const Variablen werden (normalerweise) zusammen mit allen anderen Variablen im SRAM gespeichert.
  • Literalwerte, die in verwendet werden, #definewerden häufig im Programmbereich (Flash-Speicher) neben der Skizze selbst gespeichert.

(Beachten Sie, dass es verschiedene Faktoren gibt, die genau beeinflussen können, wie und wo etwas gespeichert wird, z. B. die Compilerkonfiguration und -optimierung.)

SRAM und Flash haben unterschiedliche Einschränkungen (zB 2 KB bzw. 32 KB für das Uno). Für einige Anwendungen ist es recht einfach, kein SRAM mehr zu haben. Daher kann es hilfreich sein, einige Dinge in Flash zu verschieben. Das Gegenteil ist auch möglich, wenn auch wahrscheinlich weniger verbreitet.

PROGMEM
Es ist möglich, die Vorteile der Typensicherheit zu nutzen und gleichzeitig die Daten im Programmspeicher (Flash) zu speichern. Dies geschieht mit dem PROGMEMSchlüsselwort. Es funktioniert nicht für alle Typen, wird jedoch häufig für Arrays von ganzen Zahlen oder Zeichenfolgen verwendet.

Die allgemeine Form in der Dokumentation ist wie folgt:

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

String-Tabellen sind etwas komplizierter, aber die Dokumentation enthält alle Details.

Peter Bloomfield
quelle
1

Für Variablen eines bestimmten Typs, die während der Ausführung nicht geändert werden, kann in der Regel eine von beiden verwendet werden.

Für in Variablen enthaltene digitale PIN-Nummern kann entweder Folgendes funktionieren:

const int ledPin = 13;

Aber es gibt einen Umstand, den ich immer benutze #define

Es sollen analoge Pin-Nummern definiert werden, da diese alphanumerisch sind.

Sicher, Sie können die PIN-Nummern im gesamten Programm wie folgt fest codieren a2, a3und der Compiler weiß, wie er damit umgehen soll. Wenn Sie dann die Stifte wechseln, muss jede Verwendung geändert werden.

Außerdem möchte ich meine PIN-Definitionen immer ganz oben an einer Stelle haben, damit die Frage auftaucht, welcher Typ constfür eine als definierte PIN geeignet wäre A5.

In diesen Fällen benutze ich immer #define

Spannungsteiler Beispiel:

//
//  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)); 
 .
 .

Alle Setup-Variablen befinden sich ganz oben und es wird keine Änderung des Werts von adcPinaußer zur Kompilierungszeit geben.

Keine Sorge, welcher Typ adcPinist. In der Binärdatei wird kein zusätzlicher RAM zum Speichern einer Konstanten verwendet.

Der Compiler ersetzt einfach jede Instanz von adcPindurch den String, A5bevor er kompiliert.


Es gibt einen interessanten Arduino-Forum-Thread, in dem andere Entscheidungsmöglichkeiten diskutiert werden:

#define vs. const variable (Arduino-Forum)

Ausschnitte:

Code-Ersetzung:

#define FOREVER for( ; ; )

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

Debugging-Code:

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

Definiert trueund falseals Boolean, um RAM zu sparen

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

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

Vieles hängt von persönlichen Vorlieben ab, aber es ist klar, dass dies #definevielseitiger ist.

SDsolar
quelle
Unter den gleichen Umständen verwendet a constnicht mehr RAM als a #define. Und für die analogen Pins würde ich sie als definieren const uint8_t, obwohl const intdas keinen Unterschied machen würde.
Edgar Bonet
Sie haben geschrieben, dass " a constnicht mehr RAM verwendet, [...] bis es tatsächlich verwendet wird ". Sie haben meinen Standpunkt verfehlt: a verwendet meistens constkein RAM, auch wenn es verwendet wird . Dann ist dies ein Multipass-Compiler . Am wichtigsten ist, dass es sich um einen optimierenden Compiler handelt. Wann immer möglich, werden Konstanten in unmittelbare Operanden optimiert .
Edgar Bonet