Arduino中的全局变量是否有害?


24

我在编程方面相对较新,并且我正在有效阅读的许多编码最佳实践都指出,使用全局变量的理由很少(或者最好的代码根本没有全局变量)。

在编写用于使用SD卡制作Arduino接口的软件,与计算机对话并运行电动机控制器时,我已尽全力记住这一点。

我目前有46个全局变量,大约有1100行“入门级”代码(没有一行可以执行多个操作)。这是一个很好的比率,还是我应该考虑进一步降低它?另外,我可以采用哪些实践来进一步减少全局变量的数量?

我在这里问这个问题是因为我特别关注在Arduino产品上编码的最佳实践,而不是一般的计算机编程。


2
在Arduino中,您无法避免使用全局变量。函数/方法范围之外的每个变量声明都是全局的。因此,如果您需要在函数之间共享值,则它们必须是全局变量,除非您想将每个值都作为参数传递。

16
@LookAlterno Err,因为它只是带有奇怪宏和库的C ++,所以您不能在Ardunio中编写类吗?如果是这样,不是每个变量都是全局变量。即使在C语言中,通常也被认为是最佳做法,而不是使用全局变量,而是更喜欢将变量(也许在结构内部)传递给函数。对于小型程序而言,它可能不太方便,但通常会在程序变得更大和更复杂时得到回报。
Muzer

11
@LookAlterno:“我避免”“你不能”是非常不同的东西。
与莫妮卡(Monica)进行的轻量级比赛

2
实际上,某些嵌入式程序员禁止使用局部变量,而需要全局变量(或函数作用域的静态变量)。当程序较小时,可以简化为简单状态机的分析程序;因为变量永远不会相互覆盖(即:因为它们确实会堆叠和堆分配变量)。
罗布

1
静态变量和全局变量具有在编译时知道内存消耗的优点。对于具有非常有限的可用内存的arduino,这可能是一个优势。对于新手来说,耗尽可用内存并经历无法追踪的故障非常容易。
antipattern

Answers:


33

它们本身并不是邪恶的,但是在没有充分理由使用它们的情况下,它们的确会被滥用。

在很多时候,全局变量都是有利的。尤其是因为对Arduino进行编程与对PC进行编程有很大不同。

全局变量的最大好处是静态分配。特别是对于大型和复杂的变量,例如类实例。new由于缺乏资源,无法进行动态分配(使用等)。

同样,您不会像在普通C程序中那样获得单个调用树(单个main()函数调用其他函数),而是有效地获得了两个单独的树(先setup()调用函数,然后loop()调用函数),这意味着有时全局变量是实现目标的唯一方法(即,如果您想同时在setup()和中使用它loop())。

因此,不,它们不是邪恶的,在Arduino上,它们的使用比在PC上更多,更好。


好的,如果我仅在其中使用loop()(或在in中调用的多个函数中loop())使用该怎么办?以一种不同于最初定义它们的方式来设置它们会更好吗?
ATE-ENGE'17年

1
在那种情况下,我可能会在loop()中定义它们(也许static好像我需要它们在迭代中保留它们的值),并将它们通过函数参数传递给调用链。
马延科

2
也就是说的一种方式,或将其传递作为参考:void foo(int &var) { var = 4; }foo(n);- n现在是4
Majenko

1
类可以堆栈分配。诚然,将大型实例放在堆栈上不是很好,但是还是这样。
JAB

1
@JAB可以堆栈分配任何内容。
马延科

18

不看您的实际代码就很难给出明确的答案。

全局变量并不是邪恶的,它们通常在嵌入式环境中有意义,在该环境中您通常需要进行大量硬件访问。您只有四个UART,只有一个I2C端口,等等。因此对与特定硬件资源相关联的变量使用全局变量是有意义的。事实上,Arduino的核心库做的是:SerialSerial1,等都是全局变量。同样,代表程序全局状态的变量通常是全局变量。

我目前有46个全局变量,用于大约1100行[代码]。这是一个很好的比率[...]

不是关于数字。对于所有这些全局变量,您应该问自己的正确问题是,将其纳入全局范围是否有意义。

不过,对我来说,全球46强似乎有点高。如果其中一些保持常数,则将其限定为const:编译器通常会优化其存储。如果这些变量中的任何一个仅在单个函数内使用,请将其设为局部。如果您希望其值在两次调用函数之间保持不变,请将其限定为static。您还可以通过将一个类中的变量分组在一起并拥有该类的一个全局实例来减少“可见”全局变量的数量。但是只有在将所有内容放在一起时才这样做。制作一个大GlobalStuff的只有一个全局变量的缘故类将不会有助于使你的代码更清晰。


1
好!我不知道static是否有一个仅在一个函数中使用的变量,但是每次调用一个函数时都会获得一个新值(例如var=millis()),我应该这样做static吗?
ATE-ENGE'17年

3
编号static仅适用于需要保留值的情况。如果您在函数中所做的第一件事是将变量的值设置为当前时间,则无需保留旧值。在这种情况下,static所做的所有事情都是在浪费内存。
安德鲁

这是一个非常全面的答案,表达了支持和怀疑全球化的观点。+1
underscore_d

6

全局变量的主要问题是代码维护。读取一行代码时,很容易找到作为参数传递或在本地声明的变量的声明。查找全局变量的声明并不是那么容易(通常需要使用IDE)。

当您有许多全局变量(40个已经很多)时,很难使用一个不太长的显式名称。使用名称空间是一种阐明全局变量作用的方法。

在C中模仿名称空间的不良方法是:

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

在Intel或Arm处理器上,访问全局变量的速度比其他变量慢。arduino可能与此相反。


2
在AVR上,全局变量在RAM上,而大多数非静态本地变量都分配给CPU寄存器,这使得它们的访问速度更快。
Edgar Bonet

1
问题并不在于真正找到声明。能够快速理解代码很重要,但最终要紧随其后-真正的问题是找到代码的哪个未知部分中的哪个varmint能够使用全局声明对您遗漏的对象进行奇怪的操作开放给所有人,让他们随心所欲。
underscore_d

5

尽管在为PC编程时我不会使用它们,但对于Arduino,它们有一些好处。如果已经说过的话最多:

  • 无动态内存使用情况(在Arduino的有限堆空间中造成空白)
  • 随处可用,因此无需将它们作为参数传递(这会浪费堆栈空间)

此外,在某些情况下,尤其是在性能方面,最好使用全局变量,而不是在需要时创建元素:

  • 减少堆空间的差距
  • 动态分配和/或释放内存可能要花费大量时间
  • 变量可以被“重用”,就像全局列表中的元素列表由于多种原因而被使用,例如,一次用作SD的缓冲区,后来用作临时字符串缓冲区。

5

与所有事物一样(除了真正的邪恶的goto之外),全局变量都有其位置。

例如,如果您有一个调试串行端口或一个日志文件,您需要能够从任何地方写入该文件,那么将其全局设置通常是有意义的。同样,如果您有一些关键的系统状态信息,那么将其全局化通常是最简单的解决方案。必须将值传递给程序中的每个函数毫无意义。

正如其他人所说,仅用1000行以上的代码来看46就显得很多了,但是如果不知道自己在做什么的细节,很难说是否过度使用了它们。

但是,对于每个全球性的人,请问自己几个重要的问题:

该名称是否明确且具体,以便我不会在其他地方意外使用相同的名称?如果没有更改名称。

这需要永远改变吗?如果没有,请考虑使其成为常量。

是否需要在任何地方都可见,还是仅全局可见,以便在函数调用之间保持该值?因此,请考虑使其成为函数的局部变量并使用关键字static。

如果在我不小心的情况下通过一段代码对此进行了更改,事情真的会糟透了吗?例如,如果您有两个相关的变量,例如名称和ID号,它们是全局变量(请参见前面有关在几乎所有地方都需要一些信息时使用全局变量的说明),那么如果其中一个变量被更改而又没有其他麻烦的事情发生的话。是的,您可能只是小心一点,但有时稍微加强一点谨慎是件好事。例如,将它们放在另一个.c文件中,然后定义可以同时设置它们并允许您读取它们的函数。然后,您仅将函数包括在关联的头文件中,这样,您的其余代码只能通过已定义的函数访问变量,因此不会造成混乱。

-更新-我刚刚意识到您已经问过Arduino特定的最佳实践,而不是一般的编码,而这更像是一般的编码回复。但老实说并没有太大的区别,好的做法就是好的做法。Arduino 的startup()and loop()结构意味着在某些情况下您必须比其他平台多使用一点全局变量,但这并没有真正改变太多,无论什么情况,您总是最终追求在平台范围内可以做到的最好该平台是。


我对Arduino一无所知,但是做了很多台式机和服务器开发。gotos 有一种可以接受的用法(IMHO),它可以打破嵌套循环,比其他方法更干净,更容易理解。
坚持

如果您认为goto邪恶,请查看Linux代码。可以说,它们被当成try...catch块一样邪恶。
德米特里·格里戈里耶夫

5

他们是邪恶的吗?也许。全局变量的问题在于,它们可以在任何时间不受执行的任何功能或代码段的访问和修改。假设这可能导致难以追溯和解释的情况。因此,希望使全局变量的数量最小化,如果可能的话,使数量回到零。

可以避免吗?几乎总是可以。Arduino的问题在于,他们迫使您采用两种功能方法,即假设您setup()和您loop()。在这种特殊情况下,您将无法访问这两个函数的调用方函数的范围(可能是main())。如果有的话,您将可以摆脱所有全局变量,而使用本地变量。

图片如下:

int main() {
  setup();

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

这大概或多或少是Arduino程序主要功能的样子。然后,您最好在setup()loop()函数中都需要变量,然后在main()而不是全局范围内声明。然后,可以通过将它们作为参数传递(如果需要,可以使用指针)来使它们可用于其他两个函数。

例如:

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

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

请注意,在这种情况下,您还需要更改两个功能的签名。

因为这可能不可行也不合乎需要,所以我实际上只看到了一种无需修改强制程序结构即可从Arduino程序中删除大多数全局变量的方法。

如果我没记错的话,那么您在为Arduino编程时完全可以使用C ++,而不是C。如果您还不熟悉OOP(面向对象编程),或C ++,它可能需要一些时间来适应和一些阅读。

我的建议是创建一个Program类,并创建该类的单个全局实例。类应被视为对象的蓝图。

考虑以下示例程序:

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()Program::loop()功能。这些函数有权访问实例特定的成员变量myFirstSampleVariablemySecondSampleVariable而传统函数setup()loop()函数无权访问,因为这些变量已被标记为私有类。这个概念称为数据封装或数据隐藏。

教你OOP和/或C ++有点超出了此问题的答案范围,所以我在这里停止。

总结:应该避免使用全局变量,并且几乎总是可以大幅度减少全局变量的数量。同样在为Arduino编程时。

最重要的是,我希望我的回答对您有所帮助:)


如果愿意,可以在草图中定义自己的main()。这就是股票的样子:github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…–
per1234

单身人士实际上是一个整体,只是以一种额外的混乱方式打扮。它有同样的缺点。
patstew

@patstew您介意向我解释一下您有同样的缺点吗?我认为并非如此,因为您可以使用数据封装来谋取利益。
Arjen

@ per1234谢谢!我绝对不是Arduino专家,但是我想我的第一个建议也可以。
Arjen

2
那么,它仍然是可以在任何地方在程序中访问的全局状态,您只需通过访问它Program::instance().setup()来代替globalProgram.setup()。将相关的全局变量放入一个类/结构/命名空间可能是有益的,特别是如果仅几个相关的函数需要它们,但是单例模式不会添加任何内容。换句话说,static Program p;具有全局存储并static Program& instance()具有全局访问权限,这与simple相同Program globalProgram;
patstew

4

全局变量从不邪恶。反对他们的一般规则只是让您生存足够长的时间来获取经验,以便做出更好的决定。

什么是全局变量,是一个固有的假设,即一个事物只有一个(我们谈论的是可能包含多个事物的全局数组或映射无关紧要,但仍然包含仅存在一个事物的假设)一个这样的列表或映射,而不是多个独立的列表或映射)。

因此,在使用全局变量之前,您想问自己:是否可以想到我将不只使用其中之一?如果事实确实如此,那么您将必须修改代码以取消全局化,然后您可能会发现代码的其他部分依赖于唯一性假设,因此您还将必须对其进行修复,并且该过程变得乏味且容易出错。之所以讲授“不要使用全局变量”,是因为通常从一开始就避免全局变量的成本很小,而且避免了以后不得不支付大量成本的可能性。

但是,全局变量允许的简化假设也使您的代码更小,更快,使用更少的内存,因为它不必传递使用的是什么东西的概念,不必进行间接操作,也不必考虑是否可能没有所需的东西,等等。在嵌入式系统上,与PC上相比,您更有可能受到代码大小和/或CPU时间和/或内存的限制,因此这些节省很重要。而且许多嵌入式应用程序在要求方面也更加严格-您知道您的芯片仅具有某个外围设备,用户不能仅将另一个外围设备插入USB端口或其他设备。

想要多个似乎唯一的事物的另一个常见原因是测试–当您可以将某个组件的测试实例传递给函数时,测试两个组件之间的交互会更加容易,而尝试修改全局组件的行为则是一个棘手的主张。但是嵌入式世界中的测试与其他地方的测试往往有很大的不同,因此这可能不适用于您。据我所知,Arduino没有任何测试文化。

因此,请在有价值的时候使用globals。代码警察不会来接你。只是知道错误的选择可能会为您带来更多工作,因此,如果您不确定...


0

Arduino中的全局变量是邪恶的吗?

没有什么是天生的邪恶,包括全局变量。我将其描述为“必要的邪恶”-它可以使您的生活更加轻松,但应谨慎对待。

另外,我可以采用哪些实践来进一步减少全局变量的数量?

使用包装函数来访问您的全局变量。因此,至少您是从示波器角度进行管理的。


3
如果使用包装函数访问全局变量,则最好将变量放入这些函数中。
德米特里·格里戈里耶夫
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.