将全局重构为本地。它们应该是静态的吗?

时间:2020-03-06 14:43:57  来源:igfitidea点击:

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

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

当我尝试用局部变量替换仅存在于一个函数中的全局变量时,我陷入了困境。每一个全局变量的行为很像当地的"静态"如即使退出并重新进入该功能,也要保持其值不变。

对于多任务"静态"本地变量,全局变量最差。它们使功能无法重新输入。

有一种方法可以检查函数是否在不重新跟踪所有逻辑流的情况下继续保留变量值重新传递?

解决方案

请举例说明我们所谓的"全局"和"本地"变量

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.
}

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

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

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

假设仅在该范围内使用了全局变量(在删除全局变量时编译器/链接器应确认该范围),所以行为应接近相同。初始化时可能有问题,也可能没有问题,我不记得标准说了什么:如果静态初始化在C中同时发生在C ++中,则在执行首次到达声明时,则可能已将并发安全功能转换为非并发安全功能。

工作了一个功能是否是重入安全也需要看逻辑。除非标准另有规定(我没有检查过),否则函数不会因为声明了静态变量而自动重新进入。但是,如果它以任何重要方式使用全局或者静态,则可以假定它是不可重入的。如果没有同步,则假定它也是非并发安全的。

最后,祝你好运。听起来这段代码距离我们想要的地方还有很长的路要走...

如果编译器在初始化之前使用过变量时警告我们,请将可疑变量设为局部变量,而不在其声明中为其赋值。

如果没有更改其他代码,则不能将发出警告的任何变量设置为局部变量。

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

如果我们想要一种不完全安全的快速且肮脏的解决方案,则可以对其进行更改,然后看看有什么坏处。我建议确保我们具有可以在源代码管理中回滚的版本,并提前设置一些单元测试。

将全局变量更改为静态局部变量会有所帮助,因为已减小了修改范围。但是,并发问题仍然是一个问题,我们必须使用对这些静态变量的访问锁来解决它。

但是,我们要执行的操作是将变量的定义推入最高范围(用作局部变量),然后将其作为参数传递给需要它的任何对象。这显然需要大量的工作可能(因为它有一个连锁效应)。我们可以将类似所需的变量分组为"上下文"对象,然后将其传递。

参见设计模式封装上下文

如果全局变量真正仅在一个函数中使用,则使它们成为静态局部变量不会造成任何损失,因为无论如何它们都是全局的,这使得使用它们的函数不可重入。通过限制变量的范围,我们会有所收获。

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

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

可以设为自动局部变量的示例(我们可以在函数内放入"int nplus4;"(由于使用前已将其设置为零,因此无需将其设置为零,如果我们实际上将其设置为警告),在设置之前使用它,这是一个有用的检查):

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

在使用前先设置nplus4var。下面是一个应通过将"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]"之类的大全局变量移入函数中。