Arduino/C++ 全局变量与局部变量
Arduino/C++ GlobalVars vs Local variables
我现在正在做一个 Arduino 项目。它的作用基本上是这样的:
- 从 PC 读取串行数据
- 解析接收到的数据
- 使用此数据在 TFT 屏幕上绘图。
我目前的方法大量使用全局变量。没有它们,我将不得不使用一些嵌套函数来传递 3 层深的变量。这让我开始思考哪种方法在性能方面更好。下面是两个例子。
首先本地:
void setup() {
Serial.begin(9600);
Serial.println(firstFunction(10));
}
int firstFunction(int val)
{
return secondFunction(val+1);
}
int secondFunction(val)
{
return thirdFunction(val+1);
}
int thirdFunction(val)
{
return val + 1;
}
全球:
int x; //global var
void setup() {
Serial.begin(9600);
firstFunction(10);
Serial.println(x);
}
void firstFunction(int val)
{
x = val;
x += 1;
secondFunction();
}
void secondFunction()
{
x++;
thirdFunction();
}
void thirdFunction(val)
{
x++;
}
在 PC 方面,使用全局变量通常是不受欢迎的。但我的理解是,这主要是出于样式和可扩展性的原因。
大家怎么看?
我不认为添加引用会增加任何开销;请记住,编译器并不依赖于将其实现为指针。如果一切都在同一个编译单元中,它可能会被优化掉。几乎可以肯定,如果函数太小,它可以内联。
如果设计允许,另一种可能性是使这些函数和变量成为同一 class 的成员。
但归根结底,封装主要存在的目的是让复杂的项目可以通过更换程序员团队维护数月或数十年。
如果它是只有您会看到的小代码,则可能没有必要过度依赖良好做法。
在我看来,最好采用本地方法,在通常较小的 Arduino 程序上也是如此。一旦你有了很多全局变量,你就可以将它们放在一个结构中,并将结构的地址传递给函数。
在设置功能中特别有用,设置完成后不再占用内存。在您的版本中, x 在程序的生命周期内仍将占用 4 个字节。只需2k内存即可快速达到极限。
我会这样做:
typedef struct {
int a, b;
} entity_t;
void foo(entity_t *e) { foo2(e); foo3(e); /* do stuff */ }
void setup()
{
entity_t e = { 1, 2};
foo(&e);
Serial.println(e.a);
// automatic memory of e is released
}
根据项目的复杂程度,两种方法都可行。
反对通过函数调用堆栈将变量作为参数传递的一个考虑因素是您的微芯片上的内存量非常有限。每个局部变量和每个函数参数都放在你的堆栈上,在你的情况下,你在堆栈上多次基本相同的变量,浪费你的内存。根据您的 µC 上的 RAM 数量,一旦您开始使用要在运行时操作的字符串和其他更大的结构,这可能会变得很棘手。
如果您注意到您的 µC 突然开始出现异常 - 崩溃、挂起、产生垃圾输出等 - 可能是您的堆栈下溢到堆中或相反。如果您首先尝试避免像这样不必要的变量副本,这不太可能成为问题。
当然,根据您的程序的工作方式,反之亦然:如果您必须有一些状态,您只需要在程序的特定部分跟踪,并且您不会调用很多那里嵌套函数,最好将它保存在传递的局部变量中,因为一旦你从外部函数return,变量就会再次消失。
基本上,根本区别在于全局变量始终存在并在程序运行的整个过程中占用 space,而局部变量仅在创建它们的函数期间占用 return ]s - 但是函数参数就像函数的局部变量,因此将变量传递给另一个函数会创建第二个副本。所以,它总是有两个方面。
然而,随着复杂性的增加,我肯定会使用 类 - 按职责划分 - 使用成员或静态变量和相应的方法,而不是仅仅使用一堆全局变量。
那么您仍然可以将事物很好地捆绑在一起,而不是在全局范围内松散地浮动 space,但您并没有浪费内存。
我现在正在做一个 Arduino 项目。它的作用基本上是这样的:
- 从 PC 读取串行数据
- 解析接收到的数据
- 使用此数据在 TFT 屏幕上绘图。
我目前的方法大量使用全局变量。没有它们,我将不得不使用一些嵌套函数来传递 3 层深的变量。这让我开始思考哪种方法在性能方面更好。下面是两个例子。
首先本地:
void setup() {
Serial.begin(9600);
Serial.println(firstFunction(10));
}
int firstFunction(int val)
{
return secondFunction(val+1);
}
int secondFunction(val)
{
return thirdFunction(val+1);
}
int thirdFunction(val)
{
return val + 1;
}
全球:
int x; //global var
void setup() {
Serial.begin(9600);
firstFunction(10);
Serial.println(x);
}
void firstFunction(int val)
{
x = val;
x += 1;
secondFunction();
}
void secondFunction()
{
x++;
thirdFunction();
}
void thirdFunction(val)
{
x++;
}
在 PC 方面,使用全局变量通常是不受欢迎的。但我的理解是,这主要是出于样式和可扩展性的原因。
大家怎么看?
我不认为添加引用会增加任何开销;请记住,编译器并不依赖于将其实现为指针。如果一切都在同一个编译单元中,它可能会被优化掉。几乎可以肯定,如果函数太小,它可以内联。
如果设计允许,另一种可能性是使这些函数和变量成为同一 class 的成员。
但归根结底,封装主要存在的目的是让复杂的项目可以通过更换程序员团队维护数月或数十年。 如果它是只有您会看到的小代码,则可能没有必要过度依赖良好做法。
在我看来,最好采用本地方法,在通常较小的 Arduino 程序上也是如此。一旦你有了很多全局变量,你就可以将它们放在一个结构中,并将结构的地址传递给函数。
在设置功能中特别有用,设置完成后不再占用内存。在您的版本中, x 在程序的生命周期内仍将占用 4 个字节。只需2k内存即可快速达到极限。
我会这样做:
typedef struct {
int a, b;
} entity_t;
void foo(entity_t *e) { foo2(e); foo3(e); /* do stuff */ }
void setup()
{
entity_t e = { 1, 2};
foo(&e);
Serial.println(e.a);
// automatic memory of e is released
}
根据项目的复杂程度,两种方法都可行。
反对通过函数调用堆栈将变量作为参数传递的一个考虑因素是您的微芯片上的内存量非常有限。每个局部变量和每个函数参数都放在你的堆栈上,在你的情况下,你在堆栈上多次基本相同的变量,浪费你的内存。根据您的 µC 上的 RAM 数量,一旦您开始使用要在运行时操作的字符串和其他更大的结构,这可能会变得很棘手。
如果您注意到您的 µC 突然开始出现异常 - 崩溃、挂起、产生垃圾输出等 - 可能是您的堆栈下溢到堆中或相反。如果您首先尝试避免像这样不必要的变量副本,这不太可能成为问题。
当然,根据您的程序的工作方式,反之亦然:如果您必须有一些状态,您只需要在程序的特定部分跟踪,并且您不会调用很多那里嵌套函数,最好将它保存在传递的局部变量中,因为一旦你从外部函数return,变量就会再次消失。
基本上,根本区别在于全局变量始终存在并在程序运行的整个过程中占用 space,而局部变量仅在创建它们的函数期间占用 return ]s - 但是函数参数就像函数的局部变量,因此将变量传递给另一个函数会创建第二个副本。所以,它总是有两个方面。
然而,随着复杂性的增加,我肯定会使用 类 - 按职责划分 - 使用成员或静态变量和相应的方法,而不是仅仅使用一堆全局变量。
那么您仍然可以将事物很好地捆绑在一起,而不是在全局范围内松散地浮动 space,但您并没有浪费内存。