Introduction
To quote the C++ Language Standard, “Storage duration is the property of an object that defines the minimum potential lifetime of the storage containing the object.” Basically, it’s what tells you how long you should expect a variable to be usable. The variable might be a fundamental type, such as an int, or a complex type, such as a class. Regardless of its type, a variable is only guaranteed to last for as long as the programming language says it should.
C++ manages memory very differently from C#. For one thing, there is no requirement to have a garbage collector, and few implementations provide one. To the extent that C++ implementations do have automatic memory management, they mostly do so through smart pointers and reference counting. C++ classes do not automatically live on a heap (GC-managed or otherwise). Instead, they work much more like structures in C#.
You can push a C++ class instance onto a heap when you need to, but if you declare it locally and don’t do anything funny, then it will have an automatic duration, typically implemented using a stack, and will be automatically destroyed when the program leaves the scope in which the class exists.
C++ gives you more control over memory management than C#. A consequence of this is that the C++ language and runtime environment cannot do as much to prevent erroneous code as the C# language and the CLR can. One of the keys to being a good C++ programmer is to understand how memory management works and to use the best practices in order to write efficient, correct code.
Static Duration
Global variables, including those inside namespaces, and variables marked with the duration keyword static have static storage duration.
Global variables are initialized during program initialization (i.e. the period before the program actually starts execution of your main or wmain function). They are initialized in the order in which they are defined in the source code. It’s generally not a good idea to rely on initialization order since refactoring and other seemingly innocent changes could easily introduce a potential bug into your program.
Local statics are zero-initialized the first time program execution reaches the block containing the local static. Typically, they will be initialized to their specified values or initialized by calling the specified constructor at that point. The value assignment or construction phase is not required until the program reaches and executes the statement, except in very rare circumstances. Once a local static is initialized, the initialization specified with its declaration will never run again. This, of course, is just what we would expect of a local static. If it kept initializing itself every time the program reached its definition line, then it would be the same as a non-static local.
You can assign other values to global and local statics, unless you also make them const, of course.
Automatic Duration
Within a block, an object has automatic duration if it is defined without the new operator to instantiate it, and without a storage duration keyword, although it can optionally have the register keyword. This means the object is created at the point when it is defined and is destroyed when the program exits the block its variable was declared in, or when a new value is assigned to its variable.
Note: The auto keyword used to be a way of explicitly selecting automatic storage duration. In C++11, that usage was removed. It now is the equivalent of the var keyword in C#. If you try to compile something using the old meaning of auto, you'll receive a compiler error since auto as a type specifier must be the only type specifier.
Dynamic Duration
Dynamic duration is the result of using either the new operator or the new[] operator. The new operator is used to allocate single objects, while the new[] operator is used to allocate dynamic arrays. You must keep track of the size of a dynamically allocated array. While the C++ implementation will properly free a dynamically allocated array, provided you use the delete[] operator, there is no easy or portable way to determine the size of that allocation. It will likely be impossible. Single objects are freed with the delete operator.
When you allocate memory using new or new[], the return value is a pointer. A pointer is a variable that holds a memory address. In C#, if you set all your references to an object to null or some other value, then the memory is no longer reachable in your program, so the GC can free that memory for other uses.
In C++, if you set all of your pointers to an object to nullptr or some other value, and you cannot figure out the original address using pointer arithmetic, then you have lost your ability to release that memory using the delete or delete[] operators. You have thereby created a memory leak. If a program leaks enough memory, eventually it will crash because the system will run out of memory addresses for it. Even before that, though, the computer will slow horribly, as it is forced to increase paging to accommodate the ever-increasing memory footprint of your program (assuming it has virtual memory, which is absent from most smart phones).
Note: A const pointer, such as someStr in the statement const wchar_t* someStr = L"Hello World!";
is not a dynamic duration pointer. That memory is just part of the program itself. If you try to call delete or delete[] on it, the program will simply crash. A string is an array of characters, however, so if it were okay to delete it, then the delete[] operator would be the correct one to use.
When dealing with dynamic memory, to eliminate potential leaks and limit the possibility of other related bugs, you should always use a smart pointer such as std::unique_ptr
or std::shared_ptr
. We will discuss these in the chapter that covers pointers.
Thread Duration
Thread duration is the least commonly used storage duration. It has only recently been standardized. As of this writing, few, if any, C++ compiler vendors have implemented support for the new thread_local keyword from the C++11 standard.
This is certain to change, but for now you can use vendor-specific extensions such as the Microsoft-specific extension _declspec(thread) or the GCC-specific extension __thread if you need functionality of this sort.
Thread duration is similar to static duration except instead of lasting the life of the program, these variables are local to each thread; the thread's copy exists for the duration of the thread. Each thread's instance of a thread duration object is initialized when it is first used in the thread, and it is destroyed when the thread exits. A thread duration object does not inherit its value from the thread that started the thread it exists in.
Choosing the Right Storage Duration
Automatic storage duration is usually the right form of storage duration for objects, unless you need them to survive the scope they were created in. If that is the case, you should pick one of the remaining storage durations that best fits your needs.
- If the object should exist for the whole length of the program’s execution, use static storage duration.
- If the object should exist for the whole length of a particular thread, use thread storage duration.
- If the object will only exist for part of the program or thread’s duration, use dynamic storage duration.
You can deviate from those recommendations if doing so makes sense, but in most cases, this guidance will steer you correctly.
Storage Duration Sample
The following sample is included to help clarify these concepts. The sample is heavily documented, so no additional commentary is included. I strongly encourage you to build and run this particular sample. Seeing the output while stepping through the code will help you grasp these concepts more easily than simply reading the code.
Sample: StorageDurationSample\SomeClass.h
#pragma once #include <string> #include <memory> class SomeClass { public: explicit SomeClass(int value = 0); SomeClass( int value, const wchar_t* stringId ); ~SomeClass(void); int GetValue(void) { return m_value; } void SetValue(int value) { m_value = value; } static std::unique_ptr<SomeClass> s_someClass; private: int m_value; std::wstring m_stringId; };
Sample: StorageDurationSample\SomeClass.cpp
#include "SomeClass.h" #include <string> #include <ostream> #include <iostream> #include <ios> #include <iomanip> #include <thread> #include <memory> using namespace std; SomeClass::SomeClass(int value) : m_value(value), m_stringId(L"(No string id provided.)") { SomeClass* localThis = this; auto addr = reinterpret_cast<unsigned int>(localThis); wcout << L"Creating SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Value is '" << m_value << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; } SomeClass::SomeClass( int value, const wchar_t* stringId ) : m_value(value), m_stringId(stringId) { SomeClass* localThis = this; auto addr = reinterpret_cast<int>(localThis); wcout << L"Creating SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Value is '" << m_value << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; } SomeClass::~SomeClass(void) { // This is just here to clarify that we aren't deleting a // new object when we replace an old object with it, despite // the order in which the Creating and Destroying output is // shown. m_value = 0; SomeClass* localThis = this; auto addr = reinterpret_cast<unsigned int>(localThis); wcout << L"Destroying SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; } // Note that when creating a static member variable, the definition also // needs to have the type specified. Here, we start off with // 'unique_ptr<SomeClass>' before proceeding to the // 'SomeClass::s_someClass = ...;' value assignment. unique_ptr<SomeClass> SomeClass::s_someClass = unique_ptr<SomeClass>(new SomeClass(10, L"s_someClass"));
Sample: StorageDurationSample\StorageDurationSample.cpp
#include <iostream> #include <ostream> #include <sstream> #include <thread> #include <memory> #include <cstddef> #include "SomeClass.h" #include "../pchar.h" using namespace std; struct SomeStruct { int Value; }; namespace Value { // Visual C++ does not support thread_local as of VS 2012 RC. We can // partially mimic thread_local with _declspec(thread), but we cannot // have things as classes with functions (including constructors // and destructors) with _declspec(thread). _declspec(thread) SomeStruct ThreadLocalSomeStruct = {}; // g_staticSomeClass has static duration. It exists until the program // ends or until a different value is assigned to it. Even if you left // off the static keyword, in this case it would still be static since // it is not a local variable, is not dynamic, and is not a thread- // local variable. static SomeClass g_staticSomeClass = SomeClass(20, L"g_staticSomeClass"); } // This method creates a SomeClass instance, and then changes the // value. void ChangeAndPrintValue(int value) { // Create an identifier string. wstringstream wsStr(L""); wsStr << L"ChangeAndPrintValue thread id: '" << this_thread::get_id() << L"'"; // Create a SomeClass instance to demonstrate function-level block scope. SomeClass sc(value, wsStr.str().c_str()); // Demonstrate _declspec(thread). wcout << L"Old value is " << Value::ThreadLocalSomeStruct.Value << L". Thread id: '" << this_thread::get_id() << L"'." << endl; Value::ThreadLocalSomeStruct.Value = value; wcout << L"New value is " << Value::ThreadLocalSomeStruct.Value << L". Thread id: '" << this_thread::get_id() << L"'." << endl; } void LocalStatic(int value) { static SomeClass sc(value, L"LocalStatic sc"); //// If you wanted to reinitialize sc every time, you would have to //// un-comment the following line. This, however, would defeat the //// purpose of having a local static. You could do something //// similar if you wanted to reinitialize it in certain circumstances //// since that would justify having a local static. //sc = SomeClass(value, L"LocalStatic reinitialize"); wcout << L"Local Static sc value: '" << sc.GetValue() << L"'." << endl << endl; } int _pmain(int /*argc*/, _pchar* /*argv*/[]) { // Automatic storage; destroyed when this function ends. SomeClass sc1(1, L"_pmain sc1"); wcout << L"sc1 value: '" << sc1.GetValue() << L"'." << endl << endl; { // The braces here create a new block. This means that // sc2 only survives until the matching closing brace, since // it also has automatic storage. SomeClass sc2(2, L"_pmain sc2"); wcout << L"sc2 value: '" << sc2.GetValue() << L"'." << endl << endl; } LocalStatic(1000); // Note: The local static in LocalStatic will not be reinitialized // with 5000. See the function definition for more info. LocalStatic(5000); // To demonstrate _declspec(thread) we change the value of this // thread's Value::ThreadLocalSomeStruct to 20 from its default 0. ChangeAndPrintValue(20); // We now create a new thread that automatically starts and // changes the value of Value::ThreadLocalSomeStruct to 40. If it // were shared between threads, then it would be 20 from the // previous call to ChangeAndPrintValue. But it's not. Instead, it // is the default 0 that we would expect as a result of this being // a new thread. auto thr = thread(ChangeAndPrintValue, 40); // Wait for the thread we just created to finish executing. Note that // calling join from a UI thread is a bad idea since it blocks // the current thread from running until the thread we are calling // join on completes. For WinRT programming, you want to make use // of the PPLTasks API instead. thr.join(); // Dynamic storage. WARNING: This is a 'naked' pointer, which is a very // bad practice. It is here to clarify dynamic storage and to serve // as an example. Normally, you should use either // std::unique_ptr or std::shared_ptr to wrap any memory allocated with // the 'new' keyword or the 'new[]' keyword. SomeClass* p_dsc = new SomeClass(1000, L"_pmain p_dsc"); const std::size_t arrIntSize = 5; // Dynamic storage array. THE SAME WARNING APPLIES. int* p_arrInt = new int[arrIntSize]; // Note that there's no way to find how many elements arrInt // has other than to manually track it. Also note that the values in // arrInt are not initialized (i.e. it's not arrIntSize zeroes, it's // arrIntSize arbitrary integer values). for (int i = 0; i < arrIntSize; i++) { wcout << L"i: '" << i << L"'. p_arrInt[i] = '" << p_arrInt[i] << L"'." << endl; // Assign a value of i to this index. p_arrInt[i] = i; } wcout << endl; //// If you wanted to zero out your dynamic array, you could do this: //uninitialized_fill_n(p_arrInt, arrIntSize, 0); for (int i = 0; i < arrIntSize; i++) { wcout << L"i: '" << i << L"'. p_arrInt[i] = '" << p_arrInt[i] << L"'." << endl; } // If you forgot this, you would have a memory leak. delete p_dsc; //// If you un-commented this, then you would have a double delete, //// which would crash your program. //delete p_dsc; //// If you did this, you would have a program error, which may or may //// not crash your program. Since dsc is not an array, it should not //// use the array delete (i.e. delete[]), but should use the non-array //// delete shown previously. //delete[] p_dsc; // You should always set a pointer to nullptr after deleting it to // prevent any accidental use of it (since what it points to is unknown // at this point). p_dsc = nullptr; // If you forgot this, you would have a memory leak. If you used // 'delete' instead of 'delete[]' unknown bad things might happen. Some // implementations will overlook it while others would crash or do who // knows what else. delete[] p_arrInt; p_arrInt = nullptr; wcout << L"Ending program." << endl; return 0; }
For whom it is inconvenient to run the sample, here is the output I get when I run this from a command prompt on Windows 8 Release Preview, compiled with Visual Studio 2012 Ultimate RC in Debug configuration targeting the x86 chipset. You will probably produce different values for the addresses and thread IDs if you run it on your own system.
Creating SomeClass instance. StringId: s_someClass. Address is: '0x009fade8'. Value is '10'. Thread id: '3660'. Creating SomeClass instance. StringId: g_staticSomeClass. Address is: '0x013f8554'. Value is '20'. Thread id: '3660'. Creating SomeClass instance. StringId: _pmain sc1. Address is: '0x007bfe98'. Value is '1'. Thread id: '3660'. sc1 value: '1'. Creating SomeClass instance. StringId: _pmain sc2. Address is: '0x007bfe70'. Value is '2'. Thread id: '3660'. sc2 value: '2'. Destroying SomeClass instance. StringId: _pmain sc2. Address is: '0x007bfe70'. Thread id: '3660'. Creating SomeClass instance. StringId: LocalStatic sc. Address is: '0x013f8578'. Value is '1000'. Thread id: '3660'. Local Static sc value: '1000'. Local Static sc value: '1000'. Creating SomeClass instance. StringId: ChangeAndPrintValue thread id: '3660'. Address is: '0x007bfbf4'. Value is '20'. Thread id: '3660'. Old value is 0. Thread id: '3660'. New value is 20. Thread id: '3660'. Destroying SomeClass instance. StringId: ChangeAndPrintValue thread id: '3660'. Address is: '0x007bfbf4'. Thread id: '3660'. Creating SomeClass instance. StringId: ChangeAndPrintValue thread id: '5796'. Address is: '0x0045faa8'. Value is '40'. Thread id: '5796'. Old value is 0. Thread id: '5796'. New value is 40. Thread id: '5796'. Destroying SomeClass instance. StringId: ChangeAndPrintValue thread id: '5796'. Address is: '0x0045faa8'. Thread id: '5796'. Creating SomeClass instance. StringId: _pmain p_dsc. Address is: '0x009fbcc0'. Value is '1000'. Thread id: '3660'. i: '0'. p_arrInt[i] = '-842150451'. i: '1'. p_arrInt[i] = '-842150451'. i: '2'. p_arrInt[i] = '-842150451'. i: '3'. p_arrInt[i] = '-842150451'. i: '4'. p_arrInt[i] = '-842150451'. i: '0'. p_arrInt[i] = '0'. i: '1'. p_arrInt[i] = '1'. i: '2'. p_arrInt[i] = '2'. i: '3'. p_arrInt[i] = '3'. i: '4'. p_arrInt[i] = '4'. Destroying SomeClass instance. StringId: _pmain p_dsc. Address is: '0x009fbcc0'. Thread id: '3660'. Ending program. Destroying SomeClass instance. StringId: _pmain sc1. Address is: '0x007bfe98'. Thread id: '3660'. Destroying SomeClass instance. StringId: LocalStatic sc. Address is: '0x013f8578'. Thread id: '3660'. Destroying SomeClass instance. StringId: g_staticSomeClass. Address is: '0x013f8554'. Thread id: '3660'. Destroying SomeClass instance. StringId: s_someClass. Address is: '0x009fade8'. Thread id: '3660'.
Conclusiong
Storage duration is another important aspect of C++ so make sure that you have a good grasp of what we discussed in this article before moving on. Next up are constructors, destructors, and operators.
This lesson represents a chapter from C++ Succinctly, a free eBook from the team at Syncfusion.
Comments