对常量使用#define或const int更好吗?


26

Arduino是一个奇怪的混合体,在嵌入式世界中使用了某些C ++功能-传统上是C环境。确实,很多Arduino代码都非常像C。

C传统上将#defines用于常量。有许多的原因:

  1. 您不能使用设置数组大小const int
  2. 您不能将其const int用作case语句标签(尽管在某些编译器中确实可以使用)
  3. 您不能用初始化const另一个const

您可以在StackOverflow上检查此问题以获取更多理由。

那么,我们应该为Arduino使用什么?我倾向于#define,但是我看到一些代码正在使用,const而有些则使用了混合。


一个好的优化程序将使它无济于事
棘手怪胎

3
真?我看不到编译器将如何解决类型安全之类的问题,无法使用来定义数组长度等等。
Cyber​​gibbons 2014年

我同意。另外,如果您在下面查看我的回答,则表明在某些情况下您并不真正知道要使用哪种类型,因此#define显而易见的选择是。我的示例是命名模拟引脚-例如A5。没有合适的类型可以用作a,const因此唯一的选择是使用a,#define并让编译器在解释含义之前将其替换为文本输入。
SDsolar

Answers:


21

这是需要注意的重要const int没有在C和用C相同的行为++,所以实际上一些针对它的反对已在原来的问题,并在彼得Bloomfields广泛的答案被提到的是无效的:

  • 在C ++中,const int常量是编译时间值,用于设置数组限制(如大小写标签等)。
  • const int常量不一定占用任何存储空间。除非您采用它们的地址或声明它们为extern,否则它们通常只有一个编译时存在。

但是,对于整数常量,通常最好使用(命名或匿名)enum。我经常喜欢这是因为:

  • 它与C向后兼容。
  • 它的类型安全性几乎与const int(C ++ 11中的类型安全性一样)。
  • 它提供了一种对相关常数进行分组的自然方法。
  • 您甚至可以将它们用于某些名称空间控制。

因此,在惯用的C ++程序中,没有任何理由可用#define来定义整数常量。即使您希望保持与C的兼容性(由于技术要求,因为您是在老学校踢球,或者因为与您一起工作的人都喜欢那样),您仍然可以使用enum并且应该这样做,而不是使用#define


2
您提出了一些很好的观点(尤其是关于数组限制的信息,我还没有意识到支持Arduino IDE的标准编译器)。不能说一个编译时常量不使用任何存储是不正确的,因为它的值仍然必须在使用它的任何地方出现在代码中(即程序存储器而不是SRAM)。这意味着它会影响占用比指针更多空间的任何类型的可用Flash。
彼得·布卢姆菲尔德

1
“因此,实际上在原始问题中已经提到了一些反对它的反对意见”-为什么它们在原始问题中无效,因为据说这是C的约束?
Cyber​​gibbons

@Cyber​​gibbons Arduino是基于C ++的,因此我不清楚为什么只有C约束才有意义(除非出于某种原因您的代码也必须与C兼容)。
microtherion 2014年

3
@ PeterR.Bloomfield,关于常量不需要额外存储的观点仅限于const int。对于更复杂的类型,您可以分配存储空间是正确的,但是即使如此,您也不可能比使用#define
microtherion 2014年

7

编辑:microtherion提供了一个很好的答案,它纠正了我在这里的一些观点,特别是关于内存使用情况。


如您所确定的,在某些情况下#define,由于编译器不允许使用const变量,因此您不得不使用。同样,在某些情况下,您被迫使用变量,例如,当您需要一个值数组时(即,您不能拥有的数组#define)。

但是,在许多其他情况下,不一定有单个“正确”答案。这是我要遵循的一些准则:

类型安全性
从一般编程的角度来看,const变量通常是可取的(可能的话)。这样做的主要原因是类型安全。

#define(预处理宏)直接复制文字值成在代码中的每个位置,使得每次使用独立的。可能会导致模棱两可,因为根据使用方式/位置的不同,类型最终可能会以不同的方式解析。

const变量是永远只一种类型,这是由它的声明来确定,并在初始化期间解析。它通常需要一个显式的强制转换才能表现出不同的行为(尽管在各种情况下都可以安全地隐式地对其进行类型提升)。至少,如果发生类型问题,编译器可以(如果配置正确)发出更可靠的警告。

可能的解决方法是在中包含显式强制转换或类型后缀#define。例如:

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

但是,在某些情况下,根据使用方式的不同,该方法可能会导致语法问题。

内存使用
与通用计算不同,内存在处理诸如Arduino之类的东西时显然很宝贵。使用const变量vs.a #define可能会影响数据在内存中的存储位置,这可能会迫使您使用其中一个。

  • const 变量(通常)将与所有其他变量一起存储在SRAM中。
  • 所使用的文字值#define通常会与草图本身一起存储在程序空间(闪存)中。

(请注意,有多种因素可以准确影响事物的存储方式和存储位置,例如编译器配置和优化。)

SRAM和闪存具有不同的限制(例如,Uno分别为2 KB和32 KB)。对于某些应用程序来说,用尽SRAM很容易,因此将某些内容转移到闪存中可能会有所帮助。尽管可能不太常见,但相反的情况也是可能的。

PROGMEM
在将数据存储在程序空间(Flash)中的同时,可以获得类型安全的好处。这是使用PROGMEM关键字完成的。并非所有类型都适用,但通常用于整数或字符串数​​组。

文档中给出的一般形式如下:

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

字符串表稍微复杂一点,但是文档中有完整的详细信息。


1

对于在执行过程中未更改的指定类型的变量,通常都可以使用。

对于变量中包含的数字引脚号,二者之一都可以工作,例如:

const int ledPin = 13;

但是有一种情况我总是用 #define

由于它们是字母数字,因此将定义模拟引脚号。

当然,您可以在整个程序中将引脚号硬编码为a2a3等等,编译器将知道如何使用它们。然后,如果您更改引脚,则每次使用都需要更改。

此外,我始终希望将引脚定义放在一个位置的顶部,因此问题就变成了哪种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与const变量 (Arduino论坛)

实例:

代码替换:

#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不会比使用更多的RAM #define。对于模拟引脚,我将它们定义为const uint8_t,尽管const int没有区别。
埃德加·博内特

您写道“ a const直到实际使用才真正使用更多的RAM [...] ”。您错过了我的观点:大多数时候const即使使用RAM,也不使用RAM 。然后,“ 这是一个多遍编译器 ”。最重要的是,它是一个优化的编译器。只要有可能,常量就会被优化为立即操作数
埃德加·博内特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.