60

显然那里有很多不同的意见,从“从不!总是封装(即使它只是一个宏!) ”到“这没什么大不了的——在比不方便的时候使用它们。

所以。

具体的,具体的原因(最好有例子)

  • 为什么全局变量很危险
  • 什么时候应该使用全局变量来代替替代品
  • 对于那些试图不恰当地使用全局变量的人来说,有哪些替代方案

虽然这是主观的,但我会选择一个答案(对我来说,这最能代表每个开发人员应该与全局变量之间的爱恨关系),社区将投票给他们。

我相信新手拥有这种参考很重要,但如果存在与您的答案基本相似的另一个答案,请不要弄乱它 - 添加评论或编辑其他人的答案。

-亚当

4

16 回答 16

57

变量应始终具有较小的范围。其背后的论点是,每次增加范围时,都会有更多可能修改变量的代码,因此解决方案会产生更多的复杂性。

因此很明显,如果设计和实现自然允许,避免使用全局变量是首选。因此,除非确实需要,否则我不喜欢使用全局变量。

我也不同意“从不”的说法。与任何其他概念一样,全局变量仅应在需要时使用。我宁愿使用全局变量而不是使用一些人为的构造(比如传递指针),这只会掩盖真正的意图。

使用全局变量的一些很好的例子是单例模式实现或嵌入式系统中的寄存器访问。

关于如何实际检测全局变量的过度使用:检查,检查,检查。每当我看到一个全局变量时,我都会问自己:在全局范围内真的需要它吗?

于 2008-10-06T20:58:01.390 回答
20

使全局变量起作用的唯一方法是给它们命名以确保它们是唯一的。

该名称通常具有与某些“模块”或全局变量特别关注或有意义的函数集合相关联的前缀。

这意味着变量“属于”这些函数——它是它们的一部分。实际上,全局通常可以用一个与其他函数一起使用的小函数“包装”——在同一个.h文件的同名前缀中。

奖金。

当你这样做时,突然间,它不再是真正的全球性了。它现在是一些相关功能模块的一部分。

总是可以做到的。稍加思考,每个以前的全局变量都可以分配给一些函数集合,分配给特定.h文件,并与允许您在不破坏任何内容的情况下更改变量的函数隔离。

与其说“从不使用全局变量”,不如说“将全局变量的职责分配给最有意义的某个模块”。

于 2008-10-06T22:15:37.040 回答
11

考虑一下这个公案:“如果范围足够窄,那么一切都是全局的”。

在这个时代仍然很有可能需要编写一个非常快速的实用程序来完成一次性工作。

在这种情况下,创建对变量的安全访问所需的能量大于在如此小的实用程序中调试问题所节省的能量。

这是我能临时想到的唯一一种全局变量是明智的情况,而且这种情况相对较少。有用的、新颖的程序小到可以完全保存在大脑的短期记忆中,但它们越来越少了,但它们仍然存在。

事实上,我可以大胆地说,如果程序没有这么小,那么全局变量应该是非法的。

  • 如果变量永远不会改变,那么它是一个常数,而不是一个变量。
  • 如果变量需要通用访问,那么应该存在两个子程序来获取和设置它,并且它们应该是同步的。
  • 如果程序一开始很小,以后可能会变大,那么就好像今天的程序很大一样进行编码,并取消全局变量。并非所有程序都会增长!(当然,这假设程序员有时愿意丢弃代码。)
于 2008-10-06T21:35:40.193 回答
10

如果多个方法需要一个变量(而不是将变量传递给每个方法),C 中的全局变量有助于使代码更具可读性。但是,它们很危险,因为所有位置都可以修改该变量,从而可能难以追踪错误。如果您必须使用全局变量,请始终确保仅由一种方法直接修改它,并让所有其他调用者使用该方法。这将使调试与该变量更改相关的问题变得更加容易。

于 2008-10-06T20:44:45.427 回答
9

当您不担心线程安全代码时:在任何有意义的地方使用它们,换句话说,在任何可以将某些东西表达为全局状态的地方使用它们。

当您的代码可能是多线程时:不惜一切代价避免。将全局变量抽象到工作队列或其他一些线程安全的结构中,或者如果绝对必要的话将它们包装在锁中,记住这些可能是程序中的瓶颈。

于 2008-10-06T21:29:39.737 回答
4

我来自“从不”阵营,直到我开始在国防工业工作。有一些行业标准要求软件使用全局变量而不是动态(C 语言中的 malloc)内存。我不得不为我从事的一些项目重新考虑动态内存分配的方法。如果您可以使用适当的信号量、线程等来保护“全局”内存,那么这可能是一种可以接受的内存管理方法。

于 2008-10-06T20:56:28.430 回答
4

代码复杂性并不是唯一需要关注的优化。对于许多应用程序,性能优化具有更高的优先级。但更重要的是,在许多情况下,使用全局变量可以大大降低代码复杂性。有许多,也许是专门的,全局变量不仅是可接受的解决方案,而且是首选的情况。我最喜欢的专门示例是它们用于在应用程序的主线程与在实时线程中运行的音频回调函数之间提供通信。

建议全局变量在多线程应用程序中是一种责任是误导性的,因为任何变量,无论范围如何,如果它在多个线程上发生变化,都是潜在的责任。

谨慎使用全局变量。应尽可能使用数据结构来组织和隔离全局命名空间的使用。

可变作用域为程序员提供了非常有用的保护——但它可能有代价。今晚我来写关于全局变量的文章,因为我是一位经验丰富的 Objective-C 程序员,经常对面向对象在数据访问上设置的障碍感到沮丧。我认为,反全球的狂热主要来自年轻的、理论丰富的程序员,他们主要是孤立地使用面向对象的 API,而没有深入的系统级 API 及其在应用程序开发中的交互的实践经验。但我不得不承认,当供应商草率地使用命名空间时,我会感到沮丧。例如,一些 Linux 发行版在全球范围内预定义了“PI”和“TWOPI”,这破坏了我的大部分个人代码。

于 2010-12-03T08:55:44.497 回答
3
  • 何时不使用:全局变量是危险的,因为了解全局变量如何更改的唯一方法是跟踪声明它们的 .c 文件中的整个源代码(或所有 .c 文件,如果它是 extern as好)。如果您的代码出现错误,您必须搜索整个源文件以查看哪些函数更改了它,以及何时更改。出错时进行调试是一场噩梦。我们常常理所当然地认为局部变量概念背后的独创性优雅地超出范围 -很容易追踪
  • 何时使用:当全局变量的利用率没有被过度屏蔽并且使用局部变量的成本过于复杂以至于损害可读性时,应该使用全局变量。我的意思是必须向函数参数添加一个额外的参数,并返回和传递指针等等。三个经典示例:当我使用 pop 和 push 堆栈时 - 这是函数之间共享的。当然,我可以使用局部变量,但我必须将指针作为附加参数传递。第二个经典示例可以在 K&R 的“The C Programming Language”中找到,他们定义了getch()ungetch()共享全局字符缓冲区数组的函数。再一次,我们不需要让它成为全局的,但是当它很难弄乱缓冲区的使用时,增加的复杂性值得吗?第三个示例是您可以在 Arduino 爱好者的嵌入式空间中找到的东西。主循环函数中的许多函数都共享millis()函数,这是调用该函数的瞬时时间。因为时钟速度不是无限的,所以millis()在单个循环中会有所不同。为了使其保持不变,请在每个循环之前拍摄时间快照并将其保存在全局变量中。时间快照现在将与许多功能访问时相同。
  • 替代品:不多。尽可能坚持局部范围,尤其是在项目开始时,而不是相反。随着项目的发展,如果您觉得使用全局变量可以降低复杂性,那么就这样做,但前提是它满足第二点的要求。请记住,与不负责任地使用全局变量相比,使用局部范围和拥有更复杂的代码的危害要小一些。
于 2017-01-13T21:57:50.773 回答
2

您还需要考虑在什么上下文中使用全局变量。将来您是否希望此代码重复。

例如,如果您在系统中使用套接字来访问资源。将来您是否希望访问多个这些资源,如果答案是肯定的,我首先会远离全局变量,因此不需要进行重大重构。

于 2008-10-06T20:55:51.787 回答
1

它和其他任何经常被过度使用的工具一样,但我不认为它们是邪恶的。

例如,我有一个真正像在线数据库一样的程序。数据存储在内存中,但其他程序可以对其进行操作。有一些内部例程的行为很像数据库中的存储过程和触发器。

这个程序有数百个全局变量,但如果你仔细想想,什么是数据库,但有大量的全局变量。

这个程序已经通过许多版本使用了大约十年,它从来没有成为问题,我会在一分钟内再做一次。

我承认在这种情况下,全局变量是具有用于更改对象状态的方法的对象。因此,在调试时跟踪谁更改了对象不是问题,因为我总是可以在更改对象状态的例程上设置断点。或者更简单,我只需打开记录更改的内置日志记录。

于 2008-10-06T20:50:24.933 回答
1

当多个函数需要访问数据或写入对象时,应使用全局变量。例如,如果您必须传递数据或对多个函数的引用,例如单个日志文件、连接池或需要跨应用程序访问的硬件引用。这可以防止非常长的函数声明和重复数据的大量分配。

除非绝对必要,否则通常不应使用全局变量,因为只有在明确告知这样做或程序结束时才会清理全局变量。如果您正在运行多线程应用程序,则多个函数可以同时写入变量。如果您有错误,则跟踪该错误可能会更加困难,因为您不知道哪个函数正在更改变量。除非您使用显式为​​全局变量指定唯一名称的命名约定,否则您还会遇到命名冲突的问题。

于 2008-10-06T21:05:09.753 回答
1

当你声明常量时。

于 2008-10-06T21:20:12.487 回答
1

我能想到几个原因:

调试/测试目的(警告-尚未测试此代码):

#include <stdio.h>
#define MAX_INPUT 46
int runs=0;
int fib1(int n){
    ++runs;
    return n>2?fib1(n-1)+fib1(n-2):1;
};
int fib2(int n,int *cache,int *len){
    ++runs;
    if(n<=2){
        if(*len==2)
            return 1;
        *len=2;
        return cache[0]=cache[1]=1;
    }else if(*len>=n)
        return cache[n-1];
    else{
        if(*len!=n-1)
            fib2(n-1,cache,len);
        *len=n;
        return cache[n-1]=cache[n-2]+cache[n-3];
    };
};
int main(){
    int n;
    int cache[MAX_INPUT];
    int len=0;
    scanf("%i",&n);
    if(!n||n>MAX_INPUT)
        return 0;
    printf("fib1(%i)==%i",n,fib1(n));
    printf(", %i run(s)\n",runs);
    runs=0;
    printf("fib2(%i)==%i",n,fib2(n,&cache,&len));
    printf(", %i run(s)\n",runs);
    main();
};

我为 fib2 使用了作用域变量,但这是全局变量可能有用的另一种情况(需要存储数据以避免永远占用的纯数学函数)。

只使用一次的程序(例如用于比赛),或者需要缩短开发时间时

全局变量可用作类型化常量,其中某处的函数需要 *int 而不是 int。

如果我打算使用该程序超过一天,我通常会避免使用全局变量。

于 2011-02-19T17:28:20.363 回答
1

我相信我们公司有一个边缘案例,这阻止了我进入“从不使用全局变量阵营”。

我们需要编写一个在我们的盒子中工作的嵌入式应用程序,从医院的设备中提取医疗数据。

这应该无限运行,即使医疗设备被关闭、网络消失或我们盒子的设置发生变化。设置是从 .txt 文件中读取的,该文件可以在运行时更改,最好没有问题。

这就是为什么单例模式对我没用。所以我们不时返回(读取 1000 条数据后)并读取设置,如下所示:

public static SettingForIncubator settings;

public static void main(String[] args) {
    while(true){
        SettingsForIncubator settings = getSettings(args);

        int counter=0;

        while(medicalDeviceIsGivingData && counter < 1000){
            readData(); //using settings

            //a lot of of other functions that use settings.

            counter++;
        }
    } 
}
于 2019-12-26T15:18:01.590 回答
0

全局常量很有用 - 与预处理器宏相比,您可以获得更多的类型安全性,并且如果您决定需要更改值,它仍然同样容易。

全局变量有一些用途,例如,如果程序的许多部分的操作取决于状态机中的特定状态。只要您限制可以修改变量跟踪涉及它的错误的位置的数量就不会太糟糕。

几乎只要您创建多个线程,全局变量就会变得危险。在这种情况下,您确实应该将范围限制为(最多)一个文件全局变量(通过将其声明为静态)和 getter/setter 方法来保护它免受可能危险的多次访问。

于 2009-01-21T02:23:54.727 回答
-1

我在这里“从不”阵营;如果你需要一个全局变量,至少使用一个单例模式。这样,您就可以获得延迟实例化的好处,并且不会弄乱全局命名空间。

于 2008-10-06T20:49:18.023 回答