1. Code
  2. Mobile Development

C++ Succinctly: Storage Duration

Scroll to top
15 min read
This post is part of a series called C++ Succinctly.
C++ Succinctly: Functions and Classes
C++ Succinctly: Constructors, Destructors, and Operators

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

1
#pragma once

2
#include <string>

3
#include <memory>

4
5
class SomeClass
6
{
7
public:
8
	explicit SomeClass(int value = 0);
9
10
	SomeClass(
11
		int value,
12
		const wchar_t* stringId
13
		);
14
15
	~SomeClass(void);
16
17
	int GetValue(void) { return m_value; }
18
19
	void SetValue(int value) { m_value = value; }
20
21
	static std::unique_ptr<SomeClass> s_someClass;
22
23
private:
24
	int				m_value;
25
	std::wstring	m_stringId;
26
};

Sample: StorageDurationSample\SomeClass.cpp

1
#include "SomeClass.h"

2
#include <string>

3
#include <ostream>

4
#include <iostream>

5
#include <ios>

6
#include <iomanip>

7
#include <thread>

8
#include <memory>

9
10
using namespace std;
11
12
SomeClass::SomeClass(int value) :
13
	m_value(value),
14
	m_stringId(L"(No string id provided.)")
15
{
16
	SomeClass* localThis = this;
17
	auto addr = reinterpret_cast<unsigned int>(localThis);
18
	wcout << L"Creating SomeClass instance." << endl <<
19
		L"StringId: " << m_stringId.c_str() << L"." << endl <<
20
		L"Address is: '0x" << setw(8) << setfill(L'0') <<
21
		hex << addr << dec << L"'." << endl <<
22
		L"Value is '" << m_value << L"'." << endl <<
23
		L"Thread id: '" <<
24
		this_thread::get_id() << L"'." << endl << endl;
25
}
26
27
SomeClass::SomeClass(
28
	int value,
29
	const wchar_t* stringId
30
	) : m_value(value),
31
	m_stringId(stringId)
32
{
33
	SomeClass* localThis = this;
34
	auto addr = reinterpret_cast<int>(localThis);
35
	wcout << L"Creating SomeClass instance." << endl <<
36
		L"StringId: " << m_stringId.c_str() << L"." << endl <<
37
		L"Address is: '0x" << setw(8) << setfill(L'0') <<
38
		hex << addr << dec << L"'." << endl <<
39
		L"Value is '" << m_value << L"'." << endl <<
40
		L"Thread id: '" <<
41
		this_thread::get_id() << L"'." << endl << endl;
42
}
43
44
SomeClass::~SomeClass(void)
45
{
46
	// This is just here to clarify that we aren't deleting a 

47
	// new object when we replace an old object with it, despite 

48
	// the order in which the Creating and Destroying output is 

49
	// shown.

50
	m_value = 0;
51
	SomeClass* localThis = this;
52
	auto addr = reinterpret_cast<unsigned int>(localThis);
53
	wcout << L"Destroying SomeClass instance." << endl <<
54
		L"StringId: " << m_stringId.c_str() << L"." << endl <<
55
		L"Address is: '0x" << setw(8) << setfill(L'0') <<
56
		hex << addr << dec << L"'." << endl <<
57
		L"Thread id: '" <<
58
		this_thread::get_id() << L"'." << endl << endl;
59
}
60
61
// Note that when creating a static member variable, the definition also 

62
// needs to have the type specified. Here, we start off with 

63
// 'unique_ptr<SomeClass>' before proceeding to the 

64
// 'SomeClass::s_someClass = ...;' value assignment.

65
unique_ptr<SomeClass> SomeClass::s_someClass =
66
	unique_ptr<SomeClass>(new SomeClass(10, L"s_someClass"));

Sample: StorageDurationSample\StorageDurationSample.cpp

1
#include <iostream>

2
#include <ostream>

3
#include <sstream>

4
#include <thread>

5
#include <memory>

6
#include <cstddef>

7
#include "SomeClass.h"

8
#include "../pchar.h"

9
10
using namespace std;
11
12
struct SomeStruct
13
{
14
	int Value;
15
};
16
17
namespace Value
18
{
19
	// Visual C++ does not support thread_local as of VS 2012 RC. We can 

20
	// partially mimic thread_local with _declspec(thread), but we cannot 

21
	// have things as classes with functions (including constructors 

22
	// and destructors) with _declspec(thread).

23
	_declspec(thread) SomeStruct ThreadLocalSomeStruct = {};
24
25
	// g_staticSomeClass has static duration. It exists until the program 

26
	// ends or until a different value is assigned to it. Even if you left 

27
	// off the static keyword, in this case it would still be static since 

28
	// it is not a local variable, is not dynamic, and is not a thread- 

29
	// local variable.

30
	static SomeClass g_staticSomeClass = SomeClass(20, L"g_staticSomeClass");
31
}
32
33
// This method creates a SomeClass instance, and then changes the

34
// value.

35
void ChangeAndPrintValue(int value)
36
{
37
	// Create an identifier string.

38
	wstringstream wsStr(L"");
39
	wsStr << L"ChangeAndPrintValue thread id: '" << this_thread::get_id()
40
		<< L"'";
41
	// Create a SomeClass instance to demonstrate function-level block scope.

42
	SomeClass sc(value, wsStr.str().c_str());
43
44
	// Demonstrate _declspec(thread).

45
	wcout << L"Old value is " << Value::ThreadLocalSomeStruct.Value <<
46
		L". Thread id: '" << this_thread::get_id() << L"'." << endl;
47
	Value::ThreadLocalSomeStruct.Value = value;
48
	wcout << L"New value is " << Value::ThreadLocalSomeStruct.Value <<
49
		L". Thread id: '" << this_thread::get_id() << L"'." << endl;
50
}
51
52
void LocalStatic(int value)
53
{
54
	static SomeClass sc(value, L"LocalStatic sc");
55
56
	//// If you wanted to reinitialize sc every time, you would have to 

57
	//// un-comment the following line. This, however, would defeat the 

58
	//// purpose of having a local static. You could do something 

59
	//// similar if you wanted to reinitialize it in certain circumstances 

60
	//// since that would justify having a local static.

61
	//sc = SomeClass(value, L"LocalStatic reinitialize"); 

62
63
	wcout << L"Local Static sc value: '" << sc.GetValue() <<
64
		L"'." << endl << endl;
65
}
66
67
int _pmain(int /*argc*/, _pchar* /*argv*/[])
68
{
69
	// Automatic storage; destroyed when this function ends.

70
	SomeClass sc1(1, L"_pmain sc1");
71
	wcout << L"sc1 value: '" << sc1.GetValue() <<
72
		L"'." << endl << endl;
73
	{
74
		// The braces here create a new block. This means that 

75
		// sc2 only survives until the matching closing brace, since 

76
		// it also has automatic storage.

77
		SomeClass sc2(2, L"_pmain sc2");
78
		wcout << L"sc2 value: '" << sc2.GetValue() <<
79
			L"'." << endl << endl;
80
	}
81
82
	LocalStatic(1000);
83
	// Note: The local static in LocalStatic will not be reinitialized

84
	// with 5000. See the function definition for more info.

85
	LocalStatic(5000);
86
87
	// To demonstrate _declspec(thread) we change the value of this

88
	// thread's Value::ThreadLocalSomeStruct to 20 from its default 0.

89
	ChangeAndPrintValue(20);
90
91
	// We now create a new thread that automatically starts and 

92
	// changes the value of Value::ThreadLocalSomeStruct to 40. If it 

93
	// were shared between threads, then it would be 20 from the 

94
	// previous call to ChangeAndPrintValue. But it's not. Instead, it 

95
	// is the default 0 that we would expect as a result of this being 

96
	// a new thread.

97
	auto thr = thread(ChangeAndPrintValue, 40);
98
99
	// Wait for the thread we just created to finish executing. Note that 

100
	// calling join from a UI thread is a bad idea since it blocks 

101
	// the current thread from running until the thread we are calling 

102
	// join on completes. For WinRT programming, you want to make use 

103
	// of the PPLTasks API instead.

104
	thr.join();
105
106
	// Dynamic storage. WARNING: This is a 'naked' pointer, which is a very 

107
	// bad practice. It is here to clarify dynamic storage and to serve 

108
	// as an example. Normally, you should use either 

109
	// std::unique_ptr or std::shared_ptr to wrap any memory allocated with 

110
	// the 'new' keyword or the 'new[]' keyword.

111
	SomeClass* p_dsc = new SomeClass(1000, L"_pmain p_dsc");
112
113
	const std::size_t arrIntSize = 5;
114
115
	// Dynamic storage array. THE SAME WARNING APPLIES.

116
	int* p_arrInt = new int[arrIntSize];
117
118
	// Note that there's no way to find how many elements arrInt

119
	// has other than to manually track it. Also note that the values in

120
	// arrInt are not initialized (i.e. it's not arrIntSize zeroes, it's

121
	// arrIntSize arbitrary integer values).

122
123
	for (int i = 0; i < arrIntSize; i++)
124
	{
125
		wcout << L"i: '" << i << L"'. p_arrInt[i] = '" <<
126
			p_arrInt[i] << L"'." << endl;
127
128
		// Assign a value of i to this index.

129
		p_arrInt[i] = i;
130
	}
131
132
	wcout << endl;
133
134
	//// If you wanted to zero out your dynamic array, you could do this:

135
	//uninitialized_fill_n(p_arrInt, arrIntSize, 0);

136
137
	for (int i = 0; i < arrIntSize; i++)
138
	{
139
		wcout << L"i: '" << i << L"'. p_arrInt[i] = '" <<
140
			p_arrInt[i] << L"'." << endl;
141
	}
142
143
	// If you forgot this, you would have a memory leak.

144
	delete p_dsc;
145
146
	//// If you un-commented this, then you would have a double delete,  

147
	//// which would crash your program.

148
	//delete p_dsc;

149
150
	//// If you did this, you would have a program error, which may or may 

151
	//// not crash your program. Since dsc is not an array, it should not 

152
	//// use the array delete (i.e. delete[]), but should use the non-array 

153
	//// delete shown previously.

154
	//delete[] p_dsc;

155
156
	// You should always set a pointer to nullptr after deleting it to 

157
	// prevent any accidental use of it (since what it points to is unknown 

158
	// at this point).

159
	p_dsc = nullptr;
160
161
	// If you forgot this, you would have a memory leak. If you used 

162
	// 'delete' instead of 'delete[]' unknown bad things might happen. Some 

163
	// implementations will overlook it while others would crash or do who 

164
	// knows what else.

165
	delete[] p_arrInt; 
166
	p_arrInt = nullptr;
167
168
	wcout << L"Ending program." << endl;
169
	return 0;
170
}

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.

1
Creating SomeClass instance.
2
StringId: s_someClass.
3
Address is: '0x009fade8'.
4
Value is '10'.
5
Thread id: '3660'.
6
7
Creating SomeClass instance.
8
StringId: g_staticSomeClass.
9
Address is: '0x013f8554'.
10
Value is '20'.
11
Thread id: '3660'.
12
13
Creating SomeClass instance.
14
StringId: _pmain sc1.
15
Address is: '0x007bfe98'.
16
Value is '1'.
17
Thread id: '3660'.
18
19
sc1 value: '1'.
20
21
Creating SomeClass instance.
22
StringId: _pmain sc2.
23
Address is: '0x007bfe70'.
24
Value is '2'.
25
Thread id: '3660'.
26
27
sc2 value: '2'.
28
29
Destroying SomeClass instance.
30
StringId: _pmain sc2.
31
Address is: '0x007bfe70'.
32
Thread id: '3660'.
33
34
Creating SomeClass instance.
35
StringId: LocalStatic sc.
36
Address is: '0x013f8578'.
37
Value is '1000'.
38
Thread id: '3660'.
39
40
Local Static sc value: '1000'.
41
42
Local Static sc value: '1000'.
43
44
Creating SomeClass instance.
45
StringId: ChangeAndPrintValue thread id: '3660'.
46
Address is: '0x007bfbf4'.
47
Value is '20'.
48
Thread id: '3660'.
49
50
Old value is 0. Thread id: '3660'.
51
New value is 20. Thread id: '3660'.
52
Destroying SomeClass instance.
53
StringId: ChangeAndPrintValue thread id: '3660'.
54
Address is: '0x007bfbf4'.
55
Thread id: '3660'.
56
57
Creating SomeClass instance.
58
StringId: ChangeAndPrintValue thread id: '5796'.
59
Address is: '0x0045faa8'.
60
Value is '40'.
61
Thread id: '5796'.
62
63
Old value is 0. Thread id: '5796'.
64
New value is 40. Thread id: '5796'.
65
Destroying SomeClass instance.
66
StringId: ChangeAndPrintValue thread id: '5796'.
67
Address is: '0x0045faa8'.
68
Thread id: '5796'.
69
70
Creating SomeClass instance.
71
StringId: _pmain p_dsc.
72
Address is: '0x009fbcc0'.
73
Value is '1000'.
74
Thread id: '3660'.
75
76
i: '0'. p_arrInt[i] = '-842150451'.
77
i: '1'. p_arrInt[i] = '-842150451'.
78
i: '2'. p_arrInt[i] = '-842150451'.
79
i: '3'. p_arrInt[i] = '-842150451'.
80
i: '4'. p_arrInt[i] = '-842150451'.
81
82
i: '0'. p_arrInt[i] = '0'.
83
i: '1'. p_arrInt[i] = '1'.
84
i: '2'. p_arrInt[i] = '2'.
85
i: '3'. p_arrInt[i] = '3'.
86
i: '4'. p_arrInt[i] = '4'.
87
Destroying SomeClass instance.
88
StringId: _pmain p_dsc.
89
Address is: '0x009fbcc0'.
90
Thread id: '3660'.
91
92
Ending program.
93
Destroying SomeClass instance.
94
StringId: _pmain sc1.
95
Address is: '0x007bfe98'.
96
Thread id: '3660'.
97
98
Destroying SomeClass instance.
99
StringId: LocalStatic sc.
100
Address is: '0x013f8578'.
101
Thread id: '3660'.
102
103
Destroying SomeClass instance.
104
StringId: g_staticSomeClass.
105
Address is: '0x013f8554'.
106
Thread id: '3660'.
107
108
Destroying SomeClass instance.
109
StringId: s_someClass.
110
Address is: '0x009fade8'.
111
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.