Решение о распределении памяти, используемой для переменной или объекта, зависит от времени жизни переменной (когда оно должно быть создано и когда должно быть уничтожено), как долго оно должно существовать, а также к чему необходимо обращаться и используйте переменную.
Что касается управления памятью, то чем меньше человек-программист пытается это сделать, и чем больше программист полагается на компилятор для обработки деталей, тем больше вероятность того, что ваша программа будет работать.
Ваши конкретные примеры слишком упрощены, чтобы проиллюстрировать реальную эвристику и соображения по выбору, как распределить память для переменной. Основная проблема с вашими примерами заключается в том, что в качестве примеров не предусмотрены другие функции, поэтому нет причин, по которым глобальные должны использоваться в первую очередь.
Итак, давайте просто поговорим об идее, где находятся переменные, и некоторых соображениях об этом решении в общих чертах.
В 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
. На самом деле использование переменной кучи может быть или не быть таким дорогостоящим с точки зрения машинного времени. Существует целый ряд соображений, касающихся виртуальной памяти и времени доступа к памяти, которые необходимо учитывать.
С другой стороны, автоматические переменные обычно распределяются быстро, но в ограниченной области памяти, обычно в стеке. Так что если вам нужна большая область памяти, то автоматическая переменная, вероятно, не является хорошим решением, и один из других типов будет предпочтительным.
Статические переменные выделяются и создаются один раз, когда начинается программа. Проблема со статическими переменными состоит в том, что то, что было помещено в последний раз, это то, что будет там при следующем обращении к нему. Существует только одна копия переменной, которая используется всеми частями вашего кода. Это совместное использование, поэтому может быть много дефектов, введенных в программу с использованием глобальных переменных.
Наконец, вам нужно помнить, что предсказать, какой код сгенерирует компилятор из предоставленных вами строк исходного кода, может быть довольно сложно. Современные оптимизирующие компиляторы сделают много перемещений и устранения, а также за кулисами генерации кода.
Видимость переменных и сцепления
Как вы можете видеть на приведенных выше простых примерах, есть два соображения относительно создания переменных. Одним из них является время жизни переменной, как долго это будет необходимо. Другая - это видимость переменной, какие части вашей программы могут видеть переменную и обращаться к ней.
Глобальная статическая переменная может быть видна всем частям вашей программы, включая исходный код, который находится в других файлах, которые скомпилированы и связаны вместе для создания вашей программы. Статическая переменная файла может быть видна всем частям вашей программы в одном и том же исходном файле.
Когда вы создаете переменную, которая используется несколькими частями вашей программы, вам необходимо учитывать то, что называется межмодульное соединение . Использование глобально видимых переменных является хорошо задокументированным источником дефектов. Чем больше видимость переменной, тем больше движущихся частей вашей программы, которые могут получить доступ к переменной, тем больше вероятность того, что вы введете дефект.
Люди не умеют иметь дело со сложными, сложными системами, понимают их работу и поведение и предсказывают, что они будут делать.Использование глобальных переменных в больших программах приводит к созданию именно такой системы, которую люди не очень хорошо понимают.