什么时候可以在C中使用全局变量?

时间:2008-10-06 20:43:26

标签: c global-variables

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

所以

具体的,具体的原因(最好是一个例子)

  • 为什么全局变量是危险的
  • 当全局变量 用于代替替代
  • 那些试图不恰当地使用全局变量的人有哪些替代方案

虽然这是主观的,但我会选择一个答案(对我而言,最能代表每个开发人员应该与全局人员建立的爱/恨关系),社区将把他们投票给下面。

我认为新手有这种参考很重要,但如果存在与你的答案基本相似的其他答案,请不要混淆 - 添加评论或编辑别人的答案。

- 亚当

16 个答案:

答案 0 :(得分:43)

变量应始终具有较小的范围。这背后的论点是,每次增加范围时,您都会有更多可能修改变量的代码,从而在解决方案中引入更多复杂性。

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

我也不能同意“从不”的陈述,就像任何其他概念一样,全局变量是应该在需要时使用的工具。我宁愿使用全局变量而不是使用一些人工构造(比如传递指针),这只会掩盖真实的意图。 使用全局变量的好例子是嵌入式系统中的单例模式实现或寄存器访问。

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

答案 1 :(得分:15)

使全局变量有效的唯一方法是给它们命名,以确保它们是唯一的。

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

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

奖金。

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

这可以始终完成。稍微考虑一下,可以将每个以前的全局变量分配给某些函数集合,分配给特定的.h文件,并使用允许您在不破坏任何内容的情况下更改变量的函数进行隔离。

您可以说“将全局变量的职责分配给最有意义的模块”,而不是说“永远不会使用全局变量”。

答案 2 :(得分:11)

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

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

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

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

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

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

答案 3 :(得分:8)

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

答案 4 :(得分:8)

如果您不担心线程安全的代码:在任何有意义的地方使用它们,换句话说,只要将某些内容表达为全局状态就有意义。

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

答案 5 :(得分:4)

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

答案 6 :(得分:3)

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

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

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

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

答案 7 :(得分:2)

您需要考虑在什么情况下使用全局变量。将来您希望此代码重复。

例如,如果您在系统中使用套接字来访问资源。在将来你会想要访问多个这样的资源,如果答案是肯定的话我会首先远离全局变量,因此不需要主要的重构。

答案 8 :(得分:1)

这是一个像其他任何通常过度使用的工具,但我不认为它们是邪恶的。

例如,我有一个真正像在线数据库一样的程序。数据存储在内存中,但其他程序可以操作它。有一些内部例程与数据库中的存储过程和触发器非常相似。

这个程序有数百个全局变量但是如果你考虑它是什么是数据库但是有大量的全局变量。

这个程序已经使用了大约十年,现在通过许多版本,这从来都不是问题,我会在一分钟内再做一次。

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

答案 9 :(得分:1)

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

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

答案 10 :(得分:1)

声明常量时。

答案 11 :(得分: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使用了范围变量,但这是另一种情况,其中全局变量可能是有用的(纯数学函数需要存储数据以避免永远占用)。

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

globals作为类型常量非常有用,其中某个函数需要* int而不是int。

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

答案 12 :(得分:1)

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

答案 13 :(得分:0)

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

答案 14 :(得分:0)

全局常量非常有用 - 您可以获得比预处理器宏更多的类型安全性,如果您确定需要,它仍然可以轻松更改值。

全局变量有一些用途,例如,如果程序的许多部分的操作依赖于状态机中的特定状态。只要你限制可以修改变量的地方数量,跟踪涉及它的错误就不算太糟糕了。

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

答案 15 :(得分:0)

我认为我们公司存在一个极端情况,这使我无法进入“永不使用全局变量阵营”。

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

该功能应该无限运行,即使在关闭医疗设备,网络不通或我们的机顶盒设置更改时也是如此。设置是从.txt文件读取的,可以在运行时更改它,而最好不要麻烦。

这就是为什么Singleton模式对我没有用。因此,我们不时返回(在读取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++;
        }
    } 
}