在嵌入式系统中使用全局变量


17

我开始为产品编写固件,我是这里的新手。我浏览了许多关于不使用全局变量或函数的文章。在8位系统中使用全局变量是否有任何限制,还是完整的“否”。如何在系统中使用全局变量,或者应该完全避免使用全局变量?

我想就此主题向大家提出宝贵的建议,以使我的固件更紧凑。


这个问题并非嵌入式系统独有。一个副本可以在这里找到
伦丁2014年

@Lundin从您的链接中说:“这些天仅在内存非常有限的嵌入式环境中才有意义。在您假定嵌入式与其他环境相同并且假定编程规则在整体上相同之前,需要了解一些知识。”
endolith '18

@endolith static文件作用域与“全局”作用域不同,请参阅下面的答案。
伦丁

Answers:


31

只要牢记@Phil的准则,就可以成功使用全局变量。但是,这里有一些不错的方法来避免它们的问题,而不会使编译后的代码紧凑。

  1. 将局部静态变量用于只需要在一个函数内部访问的持久状态。

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. 使用结构将相关变量保持在一起,以使其更清楚地在何处使用和不应该使用它们。

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. 使用全局静态变量使变量仅在当前C文件中可见。这样可以防止由于命名冲突而导致其他文件中的代码意外访问。

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

最后一点,如果要在中断例程中修改全局变量并在其他地方读取它:

  • 标记变量volatile
  • 确保它对于CPU是原子的(即对于8位CPU是8位)。

要么

  • 使用锁定机制来保护对变量的访问。

volatile和/或原子var不会帮助您避免错误,您需要某种锁定/信号量,或者在写入变量时暂时屏蔽中断。
约翰·U

3
这是“工作正常”的狭义定义。我的观点是,声明一些易变的内容并不能防止冲突。同样,您的第三个示例也不是一个好主意-拥有两个具有相同名称的独立全局变量至少会使代码更难以理解/维护。
约翰·U

1
@JohnU您不应该使用volatile来防止出现种族状况,这确实无济于事。您应该使用volatile来防止嵌入式系统编译器中常见的危险的编译器优化错误。
伦丁

2
@JohnU:volatile变量的常规用法是允许在一个执行上下文中运行的代码让另一执行上下文中的代码知道发生了什么。在8位系统上,可以使用一个易失性字节来管理缓冲区,该缓冲区将保留不超过128的2的幂的字节数,该易失性字节指示放入缓冲区的总生命周期字节数(mod 256)和另一个表示取出的生命周期字节数,条件是只有一个执行上下文将数据放入缓冲区,而只有一个执行上下文从缓冲区中取出数据。
2014年

2
@JohnU:虽然可以使用某种形式的锁定或暂时禁用中断来管理缓冲区,但这确实没有必要或没有帮助。如果缓冲区必须保留128-255字节,则编码必须稍作更改,如果缓冲区必须保留更多字节,则可能有必要禁用中断,但是在8位系统上,缓冲区倾向于较小。具有较大缓冲区的系统通常可以进行大于8位的原子写入。
2014年

24

您不想在8位系统中使用全局变量的原因与您不想在任何其他系统中使用全局变量的原因相同:它们使对程序行为的推理变得困难。

只有糟糕的程序员才会挂在“不要使用全局变量”之类的规则上。好的程序员了解规则背后的原因,然后将规则更像是准则。

您的程序容易理解吗?它的行为可以预测吗?是否容易修改其中的一部分而不破坏其他部分?如果对每个问题的回答都是肯定的,那么您就在准备好的程序上。


1
@MichaelKaras所说的-了解这些东西的含义以及它们如何影响事物(以及它们如何咬你)是很重要的。
约翰·U

5

您不应该完全避免使用全局变量(简称“全局变量”)。但是,您应该明智地使用它们。过度使用全局变量的实际问题:

  • 全局变量在整个编译单元中可见。编译单元中的任何代码都可以修改全局代码。修改的结果可能会出现在评估此全局变量的任何地方。
  • 结果,全局变量使代码更难以阅读和理解。程序员必须始终牢记评估和分配全局变量的所有位置。
  • 过多使用全局变量会使代码更容易出现缺陷。

将前缀添加g_到全局变量的名称是一个好习惯。例如,g_iFlags。当您在代码中看到带有前缀的变量时,您会立即意识到它是全局变量。


2
标志不必是全局的。例如,ISR可以调用具有静态变量的函数。
Phil Frost 2014年

+1我以前没有听说过这样的把戏。我已从答案中删除了该段。该static标志将如何变为可见main()?您是在暗示具有的相同函数static可以将其返回给main()以后吗?
尼克Alexeev

那是做到这一点的一种方法。也许函数需要设置新状态,然后返回旧状态。还有很多其他方法。也许您有一个源文件带有设置标志的功能,而另一个源文件带有设置标志状态的静态全局变量。尽管从技术上讲这是C术语的“全局”,但其范围仅限于该文件,该文件仅包含需要知道的功能。也就是说,它的范围不是“全局”的,这确实是问题所在。C ++提供了其他封装机制。
Phil Frost 2014年

4
对于严重缺乏资源的8位环境,某些避免全局变量的方法(例如仅通过设备驱动程序访问硬件)可能效率太低。
Spehro Pefhany

1
@SpehroPefhany 好的程序员了解规则背后的原因,然后将规则更像是准则。当准则冲突时,好的程序员会仔细权衡天平。
Phil Frost 2014年

4

嵌入式工作中全局数据结构的优点是它们是静态的。如果您需要的每个变量都是全局变量,那么在输入函数并在堆栈上为它们腾出空间时,您绝不会意外耗尽内存。但是,那时候为什么要有功能呢?为什么不提供一个能够处理所有逻辑和流程的大型函数-像不允许GOSUB的BASIC程序。如果您对这个想法有足够的了解,您将拥有一个典型的1970年代汇编语言程序。高效,不可能维护和排除故障。

因此,请谨慎使用全局变量,例如状态变量(例如,是否每个函数都需要知道系统是否处于解释或运行状态)以及许多函数都需要查看的其他数据结构,就像@PhilFrost所说的那样,您的功能可预测?是否有可能用永不结束的输入字符串填充堆栈?这些是算法设计的问题。

请注意,static在函数内部和外部具有不同的含义。 /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c


1
许多嵌入式系统编译器静态地分配自动变量,但是会覆盖不能同时在范围内的函数使用的变量。这通常产生的内存使用量等于基于堆栈的系统的最坏情况下的可能使用率,在该系统中,实际上可能发生所有静态可能的调用序列。
2014年

4

全局变量仅应用于真正的全局状态。仅当只能有一个“地图的北边界”时,使用全局变量来表示诸如地图北边界的纬度之类的内容。如果将来代码可能必须与具有不同北部边界的多个地图一起使用,则可能需要对使用针对北部边界使用全局变量的代码进行重新设计。

在典型的计算机应用程序中,通常没有特殊的理由认为某物永远不会超过一种。但是,在嵌入式系统中,这样的假设通常更为合理。虽然可能会调用典型的计算机程序来支持多个同时用户,但典型的嵌入式系统的用户界面将设计为由单个用户与其按钮和显示进行交互来进行操作。这样,它将在任何时候具有单个用户界面状态。设计系统以使多个用户可以与多个键盘和显示器进行交互,这比为单个用户设计系统要复杂得多,并且实现时间要长得多。如果从不要求系统支持多个用户,为促进这种使用而投入的任何额外努力将被浪费。除非可能需要多用户支持,否则比花额外的时间添加多用户支持(可能永远不需要)。

嵌入式系统的一个相关因素是,在许多情况下(尤其是涉及用户界面),支持具有多个功能的唯一实用方法是使用多个线程。在没有其他对多线程的需求的情况下,使用简单的单线程设计可能比使用真正不需要的多线程来增加系统复杂性更好。如果要添加多个功能,则无论如何都需要重新设计庞大的系统,也不需要重新使用某些全局变量就可以了。


因此,保持不会相互冲突的全局变量不会有问题。例如:Day_cntr,week_cntr等计数天,一周respectively.And我相信不应该故意用多少类似于同样的目的,必须明确界定those.Thanks很多关于反应热烈全局变量的:)。
Rookie91

-1

许多人对此主题感到困惑。全局变量的定义为:

可以从程序中的任何地方访问的东西。

这与文件范围变量不同,后者由关键字声明static。这些不是全局变量,它们是局部私有变量。

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

您应该使用全局变量吗?在某些情况下还可以:

在任何其他情况下,您永远都不要使用全局变量。从来没有理由这样做。而是使用文件作用域变量,这是完全可以的。

您应该努力编写旨在完成特定任务的独立的自治代码模块。在这些模块内,内部文件范围变量应作为私有数据成员驻留。这种设计方法被称为面向对象,被广泛认为是好的设计。


为什么要下票?
m.Alin 2014年

2
认为 “全局变量”也可以用于描述对全局段(而不是文本,堆栈或堆)的分配。从这个意义上说,文件静态变量和函数静态变量是/可以是“全局”的。在这个问题的背景下,很明显全局是指范围而不是分配段(尽管OP 可能不知道这一点)。
Paul A. Clayton

1
@ PaulA.Clayton我从未听说过一个称为“全局内存段”的正式术语。您的变量将结束在下列地点之一:寄存器堆栈(运行时分配),(运行时分配)。数据段(显式初始化静态存储变量)的.bss段(归零静态存储变量),.RODATA(读(仅常量)或.text(代码的一部分)。如果它们结束于其他任何地方,那就是特定于项目的设置。
伦丁2014年

1
@ PaulA.Clayton我怀疑您所说的“全局段”是该.data段。
伦丁2014年
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.