Как решить, использовать ли мне глобальные переменные или использовать кучу? - PullRequest
0 голосов
/ 03 апреля 2019

У меня есть вопрос об управлении памятью и глобальными переменными в сравнении с кучей и о том, как решить, использовать ли переменную, пространство которой выделено из кучи, вместо глобальной переменной.

Я понимаю, что переменные, выделенные из кучи с использованием new, действуют в течение всего времени жизни программы, а глобальные переменные также служат в течение всего времени жизни программы.

Должны ли переменные кучи использоваться вместо глобальных переменных?

Например, из приведенных ниже двух подходов, которые более уместны с точки зрения скорости кода и управления памятью, и почему этот подход был бы более уместным:

#include <iostream>

int x = 5;

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

   // do stuff with the variable x

   return 0;
}

против

#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 ]

8 голосов
/ 03 апреля 2019

Использование кучи или глобальных переменных на самом деле не является альтернативой.На самом деле, если возможно, не используйте ни один из них:

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

Нет очевидной причины, почему x должен быть глобальным, и нет очевидной причины использовать ручное распределение памяти, поэтому просто используйте локальную переменную савтоматическое хранение.

1 голос
/ 03 апреля 2019

Решение о распределении памяти, используемой для переменной или объекта, зависит от времени жизни переменной (когда оно должно быть создано и когда должно быть уничтожено), как долго оно должно существовать, а также к чему необходимо обращаться и используйте переменную.

Что касается управления памятью, то чем меньше человек-программист пытается это сделать, и чем больше программист полагается на компилятор для обработки деталей, тем больше вероятность того, что ваша программа будет работать.

Ваши конкретные примеры слишком упрощены, чтобы проиллюстрировать реальную эвристику и соображения по выбору, как распределить память для переменной. Основная проблема с вашими примерами заключается в том, что в качестве примеров не предусмотрены другие функции, поэтому нет причин, по которым глобальные должны использоваться в первую очередь.

Итак, давайте просто поговорим об идее, где находятся переменные, и некоторых соображениях об этом решении в общих чертах.

В 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 содержит значение, которое никогда не меняется и используется в различных местах программы. Строительство и уничтожение происходят только один раз, когда начинается программа. Поскольку переменная никогда не изменяется, для всех методов, используемых объектом, ничего не остается, кроме затрат времени выполнения.

Автоматическая переменная в функции будет создаваться и уничтожаться каждый раз, когда функция вводится и затем покидает функцию. То же самое относится к переменной Heap, которая создается с помощью оператора new каждый раз, когда функция вводится и уничтожается до ее выхода. Таким образом, характеристики переменной Auto и Heap в этом сценарии очень похожи.

И переменная Auto, и переменная Heap должны быть переданы любой другой функции, которая будет использовать эту переменную. Для глобальной переменной передача значения или адреса не требуется, поскольку глобальная переменная видима для вызываемой функции. Это поднимает связанную тему о том, как переменные передаются в функции, Pass by Value и Pass by Reference. См. В чем разница между передачей по ссылке и передачей по значению?

Если вы используете переменные кучи, вы должны обработать больше подробностей об управлении памятью, чтобы вы могли с большей вероятностью ввести дефект при этом. Умные указатели, такие как std::unique_ptr, могут помочь, но есть еще кое-что, о чем нужно подумать.

Использовать автоматические переменные проще для программиста, поскольку компилятор обрабатывает, когда они создаются и когда они уничтожаются. А компилятор выдаст предупреждения и ошибки о том, существует ли конкретная автоматическая переменная или нет, или она видима или нет.

Существуют некоторые накладные расходы на использование переменных кучи с созданием и уничтожением, которые могут или не могут быть рассмотрены. Другими словами, использование переменной кучи означает, что вы должны использовать распределитель памяти для создания его с помощью оператора new, а также для его уничтожения с помощью оператора delete. На самом деле использование переменной кучи может быть или не быть таким дорогостоящим с точки зрения машинного времени. Существует целый ряд соображений, касающихся виртуальной памяти и времени доступа к памяти, которые необходимо учитывать.

С другой стороны, автоматические переменные обычно распределяются быстро, но в ограниченной области памяти, обычно в стеке. Так что если вам нужна большая область памяти, то автоматическая переменная, вероятно, не является хорошим решением, и один из других типов будет предпочтительным.

Статические переменные выделяются и создаются один раз, когда начинается программа. Проблема со статическими переменными состоит в том, что то, что было помещено в последний раз, это то, что будет там при следующем обращении к нему. Существует только одна копия переменной, которая используется всеми частями вашего кода. Это совместное использование, поэтому может быть много дефектов, введенных в программу с использованием глобальных переменных.

Наконец, вам нужно помнить, что предсказать, какой код сгенерирует компилятор из предоставленных вами строк исходного кода, может быть довольно сложно. Современные оптимизирующие компиляторы сделают много перемещений и устранения, а также за кулисами генерации кода.

Видимость переменных и сцепления

Как вы можете видеть на приведенных выше простых примерах, есть два соображения относительно создания переменных. Одним из них является время жизни переменной, как долго это будет необходимо. Другая - это видимость переменной, какие части вашей программы могут видеть переменную и обращаться к ней.

Глобальная статическая переменная может быть видна всем частям вашей программы, включая исходный код, который находится в других файлах, которые скомпилированы и связаны вместе для создания вашей программы. Статическая переменная файла может быть видна всем частям вашей программы в одном и том же исходном файле.

Когда вы создаете переменную, которая используется несколькими частями вашей программы, вам необходимо учитывать то, что называется межмодульное соединение . Использование глобально видимых переменных является хорошо задокументированным источником дефектов. Чем больше видимость переменной, тем больше движущихся частей вашей программы, которые могут получить доступ к переменной, тем больше вероятность того, что вы введете дефект.

Люди не умеют иметь дело со сложными, сложными системами, понимают их работу и поведение и предсказывают, что они будут делать.Использование глобальных переменных в больших программах приводит к созданию именно такой системы, которую люди не очень хорошо понимают.

1 голос
/ 03 апреля 2019

Лучше всего стараться избегать глобальных переменных в максимально возможной степени. Куча - лучшее место для хранения данных, занимающих много места. Если вы храните много данных в стеке, вы получите перегрузку стека. В этом простом примере вам, очевидно, не нужно думать о перегрузке стека. Но если вам придется выбирать между примерами, я бы использовал кучу.

0 голосов
/ 03 апреля 2019

Зависит.Я обычно предпочитаю глобальные переменные, но вот компромиссы.

Глобальные переменные используют статическое распределение памяти, почти всегда лучше, чем динамические.Ошибки нехватки памяти невозможны, память для них выделяется компилятором во время компиляцииНе нужно беспокоиться об утечках памяти, фрагментации адресного пространства и других проблемах, связанных с динамическим управлением памятью.

Глобальные переменные увеличивают размер двоичного файла.Если вам нужно хранить в них мегабайты данных, динамическое распределение будет работать лучше, например, статически распределенные глобальные std::vector или std::unique_ptr

Если данные в переменных являются изменяемыми, доступ к глобальным переменным из более чемВ одном месте код может быть очень сложным для работы, особенно для отладки.Относится только к средним и крупным проектам.Простой обходной путь, сделайте их статичными и / или поместите их в анонимные пространства имен и передайте ссылки вместо прямого доступа к ним из различных функций.

Еще одна вещь.Если вы кодируете приложение, а не библиотеку, и оно является однопоточным, для изменчивых глобальных вещей рассмотрите возможность использования локальных переменных в области действия вашей основной функции.Распределение стека также не использует динамическую память.

0 голосов
/ 03 апреля 2019

Ограничьте глобальные переменные настолько, насколько вы можете.

Тем не менее, я видел множество глупых кодов, передающих, например, дескриптор окна верхнего уровня из функции в функцию, просто чтобы избежать установки простогоглобальная переменная (аналогично повторению std :: 100 раз в функции, потому что «использование пространства имен std» - это «плохо»).

Швейцарской армии нет;используйте глобалы, когда это необходимо.Вы можете использовать их также в пространстве имен или в качестве статических членов класса, чтобы избежать глобального пространства имен (но, опять же, иметь верхний hwnd в пространстве имен и продолжать использовать myapp: hwnd также странно).

0 голосов
/ 03 апреля 2019

Если вам абсолютно необходимы глобальные переменные , вот один из способов сделать это:

#include <iostream>

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

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

Таким образом, вы можете использовать std :: lock_guard или что-то подобное, чтобы избежать условия race в многопоточных приложениях, и это даст вам больше гибкости.

0 голосов
/ 03 апреля 2019

Вы должны использовать

static (global variable storage) для переменных, необходимых для полного запуска программы.

stack (local variable) для переменных, необходимых для вашей функции.

heap (dynamic storage)для большого пула памяти, динамических переменных и т. д.

...