【问题标题】:Refactoring global to local. Should they be static or not?将全局重构为局部。它们应该是静态的吗?
【发布时间】:2008-09-25 16:46:58
【问题描述】:

我正在重构“意大利面条代码”C 模块以在多任务 (RTOS) 环境中工作。

现在,有很长的函数和许多不必要的全局变量。

当我尝试用局部变量替换仅存在于一个函数中的全局变量时,我陷入了两难境地。每个全局变量的行为都类似于本地“静态” - 例如即使您退出并重新进入该函数,也保持其值。

对于多任务处理,“静态”本地变量比全局变量最差。它们使函数不再重新输入。

有没有一种方法可以检查函数是否在保留变量值重新进入而不跟踪所有逻辑流的情况下进行中继?

【问题讨论】:

    标签: c refactoring static global-variables rtos


    【解决方案1】:

    简短的回答:不,没有任何方法可以根据局部变量的声明是否为静态来自动判断函数的行为是否不同。您只需检查原始代码中使用全局变量的每个函数的逻辑。

    但是,如果用静态局部范围变量替换全局变量意味着该函数不是可重入的,那么当它是全局时它也不是可重入的。因此,我认为将全局变量更改为静态局部范围变量不会使您的函数比开始时的可重入性更少。

    如果全局确实仅在该范围内使用(当您删除全局时编译器/链接器应确认),则行为应该接近相同。初始化时可能会或可能不会出现问题,我不记得标准是怎么说的:如果静态初始化在 C 中与在 C++ 中发生的时间相同,那么当执行第一次到达声明时,您可能已经更改了将并发安全函数转换为非并发安全函数。

    判断一个函数是否可以安全重入还需要查看逻辑。除非标准另有说明(我没有检查过),否则函数不会仅仅因为它声明一个静态变量而自动不可重入。但是,如果它以任何重要的方式使用全局或静态,您可以假设它是不可重入的。如果没有同步,则假设它也是非并发安全的。

    最后,祝你好运。听起来这段代码距离您想要的位置还有很长的路要走......

    【讨论】:

    • 通过互斥锁保护全局变量多任务处理更容易,避免处理过程范围的静态变量。
    • 没有什么可以阻止您使用互斥锁保护局部静态变量,就像使用全局变量一样。事实上,如果一个全局变量有一个相关的互斥体,你也应该在范围内移动那个互斥体。
    • 也许它与实现相关,但我正在使用的 RTOS 中的互斥锁始终是全局的。因此,将受保护的变量设为全局变量对我来说更直观。
    • 好点——虽然他提到了多线程,但他询问了重入(“在退出和进入时保持值”),所以这就是我最初的回答。即使在单线程系统上,重入也可能失败,并且锁定也无济于事。但也许我回答错了问题。
    • +1 表示将全局移动到静态局部对函数是否实际可重入没有影响。
    【解决方案2】:

    如果您的编译器会在变量在初始化之前使用时发出警告,则将可疑变量设为本地变量,而不在其声明中为其分配值。

    如果不更改其他代码,任何发出警告的变量都不能成为本地变量。

    【讨论】:

    • 你可以有一个总是分配给变量的函数(从而避免警告),但返回一个指向它的指针(这意味着如果变量是自动的,它将中断)。
    • 我意识到这与你所说的并不矛盾,但这确实意味着测试只在一个方向上起作用:警告意味着变量是“坏的”,但“没有警告”没有这并不意味着它很好。
    【解决方案3】:

    将全局变量更改为静态局部变量会有所帮助,因为修改的范围已经缩小。然而,并发问题仍然是一个问题,您必须通过锁定访问这些静态变量来解决它。

    但是您要做的是将变量的定义推入它用作局部变量的最高范围,然后将其作为参数传递给任何需要它的东西。这显然需要大量工作(因为它具有级联效应)。您可以将类似需要的变量分组到“上下文”对象中,然后将它们传递出去。

    查看设计模式Encapsulate Context

    【讨论】:

      【解决方案4】:

      如果你的全局变量真的只用在一个函数中,那么将它们变成静态局部变量不会有任何损失,因为它们是全局变量,因此使用它们的函数是不可重入的。通过限制变量的范围,您会有所收获。

      您应该对仅在一个函数中使用的所有全局变量进行更改,然后检查每个静态局部变量,看看是否可以将其设为非静态(自动)。

      规则是:如果变量在被设置之前在函数中使用过,则保持静态。

      一个可以自动本地化的变量示例(您可以将“int nplus4;”放在函数内(您不需要将其设置为零,因为它在使用前设置,如果您使用它应该会发出警告)在设置之前实际使用它,一个有用的检查):

      int nplus4 = 0;         // used only in add5
      int add5 (int n) {
          nplus4 = n + 4;     // set
          return nplus4 + 1;  // use
      }
      

      nplus4 变量在使用前设置。以下是通过在函数中放置“static int nextn = 0;”应保持静态的示例:

      int nextn = 0;             // used only in getn
      int getn (void) {
          int n = nextn++;       // use, then use, then set
          return n;
      }
      

      请注意,它可能会变得棘手,“nextn++”不是设置,而是使用和设置,因为它等效于“nextn = nextn + 1”。

      还有一点需要注意:在 RTOS 环境中,堆栈空间可能比全局内存更有限,因此在将“char buffer[10000]”等大型全局变量移入函数时要小心。

      【讨论】:

        【解决方案5】:

        请举例说明您所谓的“全局”和“局部”变量

        int global_c; // can be used by any other file with 'extern int global_c;'
        
        static int static_c; // cannot be seen or used outside of this file.
        
        int foo(...)
        {
           int local_c; // cannot be seen or used outside of this function.
        }
        

        如果您提供一些代码示例来说明您拥有的内容和更改的内容,我们可以更好地回答这个问题。

        【讨论】:

          【解决方案6】:

          如果我正确理解了您的问题,您担心的是全局变量从一个函数调用到下一个函数调用会保留其值。显然,当您转而使用普通的局部变量时,情况并非如此。如果您想知道更改它们是否安全,我认为您除了阅读和理解代码之外别无选择。简单地对相关变量的名称进行全文搜索可能是有益的。

          如果您想要一个不完全安全的快速而肮脏的解决方案,您可以更改它,看看有什么问题。我建议确保你有一个可以在源代码控制中回滚的版本,并提前设置一些单元测试。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-02-04
            • 1970-01-01
            • 2011-12-11
            • 2015-10-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多