Deciding the memory allocation used for a variable or object is tied to the life time of the variable (when should it be created and when should it be destroyed), how long it needs to exist as well as what needs to access and use the variable.
So far as memory management is concerned, the less the human programmer tries to do it and the more that the programmer relies on the compiler to handle the details, the more likely your program is going to work.
Your particular examples are too simplistic to illustrate the actual heuristics and considerations for choosing how to allocate the memory for a variable. The main problem with your examples is that there are no other functions provided as part of the examples so no reason why a global should be used in the first place.
So lets just talk about the idea of where variables reside and some considerations about that decision in general terms.
In C++ most programs use three types of memory allocation: (1) static variables, (2) auto variables, and (3) heap variables.
Static variables are created at the time the program begins running and last as long as the program ends. However the visibility of the variable can vary between global visibility, file visibility, or within a particular scope.
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;
}
Auto variables or what are commonly called stack variables are created at the time the thread of execution reaches the point where the variable is defined. These variables last as long as the thread of execution is within the scope where the variable is defined. As soon as the execution leaves the scope, the variable is destroyed.
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.
Heap variables are created at the time the new
operator is used to create the variable. They last until the variable is destroyed using the delete
operator.
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;
}
Note: Notice an important point in the above sample program, the heap variable (the memory allocated for a variable from the heap) is different from the pointer variable used to hold the address provided by the new
operator. Should the pointer variable go out of scope, is destroyed, before the heap variable is destroyed using the delete
operator, the heap variable will continue to exist even though the ability to access it is gone with the destruction of the pointer variable that contained its address.
You can also run into a problem should you delete
the heap memory and then try to use the address contained in the pointer variable after doing so. Once you do a delete
you no longer own the memory allocated with the new
operator. It belongs to the memory allocator at that time.
Using smart pointers such as std::unique_ptr
was introduced to address the mistakes made of a pointer going out of scope before the memory it pointed to was deleted or deleting memory and they trying to use the address in the pointer later. By using one of the smart pointer types for your pointer variable, the compiler will generate the necessary code to perform the delete
for you when the smart pointer goes out of scope.
To Summarize
In all three types mentioned there are some costs that are borne by all three: cost of construction, cost of destruction. These costs are encumbered at different times. And in some cases the cost may be only once and sometimes multiple times.
Where global variables really shine is when it is a const
that holds a value that never changes and is used in various places in the program. The construction and destruction are only once, when the program begins. Since the variable is never changed, there are nothing other than runtime costs for any methods used by the object.
An Auto variable in a function is going to be constructed and destructed every time the function is entered into and then left. The same applies to a Heap variable that is created with the new
operator every time the function is entered and destroyed before the function is left. So the characteristics of Auto and Heap variable are very similar in this scenario.
Both an Auto variable and a Heap variable must be passed to any other function that will be using the variable. With a global variable this passing of the value or address is not required since the global variable is visible to the function being called. This brings up the related topic of how variables are passed to functions, Pass by Value and Pass by Reference. See What's the difference between passing by reference vs. passing by value?
If you are using heap variables, you must handle more of the details about managing memory so you are more likely to introduce a defect by doing so. Smart pointers such as std::unique_ptr
can help but there is still something that has to be managed and thought about.
Using auto variables is easier on the programmer since the compiler handles when they are created and when they are destroyed. And the compiler will provide warnings and errors about whether a particular auto variable exists or not or is visible or not.
There is some runtime overhead to using heap variables with the creation and destruction that may or may not be a consideration. In other words using a heap variable means that you must use the memory allocator to create it with the new
operator as well as to destroy it with the delete
operator. Actually using the heap variable may or may not be that costly in terms of machine time. There are a whole host of considerations involving virtual memory and memory access time that must be considered there.
On the other hand auto variables are typically allocated quickly but in a limited size memory area, normally the stack. So if you need a large memory area then an auto variable is probably not a good solution and one of the other types would be preferable.
Static variables are allocated and created once, when the program begins. The problem with static variables is that what was last put there is what will be there for the next time it is accessed. There is only one copy of the variable that is shared by what ever parts of your code are using it. This sharing is why there can be some many defects introduced into program by using global variables.
Finally one thing you need to remember is that predicting what code the compiler will generate from the source lines you provide can be pretty difficult. Modern optimizing compilers will do a lot of moving about and elimination as well as behind the scenes generation of code.
Visibility of variables and Coupling
As you can see in the above, simple examples, there are two considerations about the creating of variables. One is the lifetime of the variable, how long it will be needed. The other is the visibility of the variable, what parts of your program can see the variable and access it.
A global static variable can be seen by all parts of your program including source code that is in other files which are compiled and linked together to create your program. A file static variable can be seen by all parts of your program in the same source file.
When you create a variable that is shared by multiple parts of your program you need to consider what is called inter-module coupling. Using globally visible variables is a well documented source of defects. The greater the visibility of a variable, the more moving parts of your program that can access a variable, the more likely you are to introduce a defect.
Human beings are not good at dealing with intricate, complex systems and understanding their operation and behavior and predicting what they will do. Using global variables in large programs results in exactly this kind of system, the kind that human beings aren't very good at understanding.