如何决定应该使用全局变量还是应该使用堆?

时间:2019-04-03 12:11:00

标签: c++ variables memory heap global

我有一个关于内存管理和全局变量与堆的问题,以及如何决定是否使用从堆分配空间的变量而不是全局变量的问题。

我了解到,使用new从堆分配的变量会持续整个程序的生命周期,而全局变量也会持续整个程序的生命周期。

应该使用堆变量代替全局变量吗?

例如,以下两种方法在代码速度和内存管理方面更合适,以及为什么这种方法更合适:

#include <iostream>

int x = 5;

int main(int argc, char** argv)
{

   // do stuff with the variable x

   return 0;
}

vs

#include <iostream>

int main(int argc, char** argv)
{
   int x = new int;
   *x = 5;

   // do stuff with the variable pointed to by x

   delete x;
   return 0;
}

7 个答案:

答案 0 :(得分:8)

使用堆或使用全局变量并不是真正的选择。实际上,如果可能,请勿使用其中任何一个:

#include <iostream>
int main()
{
   int x = 5;
}

x应该是全局的,没有明显的原因,也没有使用手动内存分配的明显原因,因此,只需使用具有自动存储功能的局部变量即可。

答案 1 :(得分:1)

最好是尽量避免使用全局变量。堆是存储占用大量空间的数据的最佳位置。如果您在堆栈上存储大量数据,则会导致堆栈过载。在这个简单的示例中,您显然不必考虑堆栈重载。但是,如果必须在示例之间进行选择,我将使用堆。

答案 2 :(得分:1)

确定用于变量或对象的内存分配与变量的生命周期(何时创建变量以及何时销毁变量),变量需要存在多长时间以及需要访问什么对象以及使用变量。

就内存管理而言,人类程序员尝试执行此操作的次数越少,并且程序员依赖编译器处理细节的次数越多,您的程序就越有可能运行。

您的特定示例过于简单,无法说明实际的启发式方法以及选择如何为变量分配内存的注意事项。您的示例的主要问题是示例中没有提供其他功能,因此没有理由首先使用全局变量。

因此,让我们仅讨论变量驻留位置的想法以及有关该决策的一些注意事项。

在C ++中,大多数程序使用三种类型的内存分配:(1)静态变量,(2)自动变量和(3)堆变量。

静态变量在程序开始运行时创建,并且一直持续到程序结束。但是,变量的可见性可以在全局可见性,文件可见性或特定范围内变化。

int x = 5;  // static, global variable created when the program starts.
static int x2 = 7;  // static, file visible variable created when the program starts.

int main ()
{
    // the visibility of the following static variable is not global but only
    // within the scope of main() but it exists for the life time of the program.
    static int y = 3;   // static, local variable created when the program starts

    x = 12;

    {     // create a new scope
        static int y2 = 18;  // static, local variable visible only within this scope
        // some other stuff
     }   // end of scope, y2 is no longer visible but it still exists.

    // other stuff
    return x;
}

在执行线程到达定义变量的点时,将创建自动变量或通常称为堆栈变量的变量。只要执行线程在定义该变量的范围内,这些变量就会持续存在。一旦执行离开作用域,该变量将被销毁。

int main ()
{
    int x = 5;    // Auto variable created when this line of code is reached.

    {             // new scope created within this function
        int x2 = 3;  // Auto variable created when this line of code is reached
         // other stuff
    }             // end of new scope, auto variable x2 is destroyed

    //   other stuff
    return x;
}    // end of function scope, auto variable x is destroyed.

堆变量是在使用new运算符创建变量时创建的。它们持续到使用delete运算符销毁变量为止。

int main ()
{
    int *xp = new int;   // heap variable created and held in pointer

    {                        // new scope
       int *xp2 = new int;   // heap variable created and held in auto pointer variable
       int *xp3 = new int;   // heap variable created and held in auto pointer
       // other stuff
       delete xp2;           // heap variable destroyed
     }                // auto pointer variables xp2 and xp3 destroyed. heap variable whose address was in xp3 is NOT destroyed. memory leak.

    return x;
}

注意:请注意,在上面的示例程序中有一个要点,堆变量(为堆中的变量分配的内存)不同于用于保存由指针提供的地址的指针变量new运算符。如果指针变量超出范围并被销毁,则在使用delete运算符销毁堆变量之前,即使指针指针销毁后访问变量的能力已消失,该堆变量仍将继续存在包含其地址。

如果您delete堆内存,然后尝试使用指针变量中包含的地址,也可能会遇到问题。完成delete后,您将不再拥有new运算符分配的内存。当时它属于内存分配器。

引入了诸如std::unique_ptr之类的智能指针,以解决由于指针所指向的内存被删除或删除内存之前指针超出范围而导致的错误,并且它们稍后尝试使用指针中的地址。通过为指针变量使用一种智能指针类型,编译器将生成必要的代码,以在智能指针超出范围时为您执行delete

总结

在上述所有三种类型中,所有三种都需要承担一些费用:建筑费用,破坏费用。这些费用在不同的时间受到负担。在某些情况下,成本可能只有一次,有时甚至是多次。

全局变量真正发挥作用的地方是const拥有一个永不改变的值,并在程序的各个位置使用它。该程序开始时,构造和销毁仅一次。由于该变量从不更改,因此对象使用的任何方法都只需要运行时成本即可。

每次进入然后离开函数时,都会在函数中构造和破坏Auto变量。每次进入函数并在离开函数之前销毁该函数时,new运算符创建的Heap变量也是如此。因此,在这种情况下,Auto和Heap变量的特征非常相似。

必须将Auto变量和Heap变量都传递给将使用该变量的任何其他函数。使用全局变量时,不需要传递值或地址,因为全局变量对于所调用的函数是可见的。这涉及到如何将变量传递给函数的相关主题,即按值传递和按引用传递。参见What's the difference between passing by reference vs. passing by value?

如果使用堆变量,则必须处理有关管理内存的更多详细信息,这样您就更有可能引入缺陷。像std::unique_ptr这样的智能指针可以提供帮助,但是仍然需要管理和考虑一些事情。

在程序员上使用自动变量比较容易,因为编译器会在创建和销毁它们时进行处理。并且编译器将提供警告和错误,提示特定的自动变量是否存在或不可见。

使用堆变量进行创建和销毁会带来一些运行时开销,这可能会考虑也可能不会考虑。换句话说,使用堆变量意味着您必须使用内存分配器通过new运算符创建它,以及使用delete运算符销毁它。实际上,就机器时间而言,使用heap变量可能会或可能不会那么昂贵。在那里必须考虑很多与虚拟内存和内存访问时间有关的注意事项。

另一方面,自动变量通常被快速分配,但是分配在有限大小的存储区中,通常是堆栈。因此,如果您需要较大的内存区域,那么自动变量可能不是一个好的解决方案,而其他类型之一则更可取。

当程序开始时,静态变量将被分配并创建一次。静态变量的问题在于,上次放置的内容将在下次访问时存在。只有变量的一个副本被代码的任何部分共享。这种共享就是为什么使用全局变量会在程序中引入许多缺陷的原因。

最后,您需要记住的一件事是,很难预测编译器将从您提供的源代码行生成什么代码。现代优化的编译器将在代码生成的幕后进行大量的移动和消除。

变量和耦合的可见性

正如您在上面的简单示例中所看到的,关于变量的创建有两个注意事项。一个是变量的生存期,需要多长时间。另一个是变量的可见性,程序的哪些部分可以看到并访问该变量。

程序的所有部分都可以看到全局静态变量,包括其他文件中的源代码,这些文件已被编译并链接在一起以创建程序。程序的所有部分都可以在同一源文件中看到文件静态变量。

在创建程序的多个部分共享的变量时,您需要考虑所谓的inter-module coupling。使用全局可见的变量是有据可查的缺陷来源。变量的可见性越高,程序中访问变量的部分越多,则引入缺陷的可能性就越大。

人类不善于处理复杂的复杂系统,不善于理解其操作和行为并预测其将要做什么。在大型程序中使用全局变量会产生这种类型的系统,这种类型的人不是很擅长理解。

答案 3 :(得分:0)

您应该使用

static (global variable storage) for var需要程序的整个运行。

stack (local variable)表示函数中所需的变量。

heap (dynamic storage)用于大量的内存,动态变量等

答案 4 :(得分:0)

If you absolutely need global variables,这是一种实现方法:

#include <iostream>

auto& x()
{
  static auto data = 5;
  return data;
}

int main() 
{
  std::cout << x() << std::endl;
  return 0;
}

这样,您可以在多线程应用程序中使用std::lock_guard或类似的方法来避免race condition,它为您提供了更大的灵活性。

答案 5 :(得分:0)

尽可能限制全局变量。

也就是说,我已经看到很多愚蠢的代码,例如,从函数到函数的顶级Window句柄只是为了避免设置简单的全局变量(类似于在函数中重复std :: 100次,因为“使用命名空间标准”是“错误的”)。

没有瑞士军队;必要时使用全局变量。您也可以在名称空间中使用它们,也可以将它们用作静态类成员,以避免使用全局名称空间(但同样,在名称空间中具有顶部hwnd并继续使用myapp:hwnd也很奇怪)。

答案 6 :(得分:0)

取决于。我通常更喜欢全局变量,但这里需要权衡。

全局变量使用静态内存分配,几乎总是比动态更好。不可能出现内存不足错误,链接器会在编译时为它们分配内存。无需担心内存泄漏,地址空间碎片以及动态内存管理附带的其他问题。

全局变量会增加二进制大小。如果您需要在其中保留兆字节的数据,则动态分配会更好地工作,例如静态分配的全局std::vectorstd::unique_ptr

如果变量中的数据是可变的,则从多个位置访问全局变量会使代码非常难以处理,尤其是调试。仅适用于大中型项目。简单的解决方法是,将它们设置为静态,和/或将其放置在匿名名称空间中,并传递引用,而不是直接从不同的函数访问它们。

还有一件事。如果您要编写的应用程序不是库,而是库的单线程,那么对于可变的全局对象,请考虑在主函数范围内使用局部变量。堆栈分配也不使用动态内存。