Статьи

C ++ сжато: продолжительность хранения

Цитируя стандарт языка C ++, «Продолжительность хранения — это свойство объекта, которое определяет минимальный потенциальный срок службы хранилища, содержащего объект». По сути, это то, что говорит вам, как долго вы должны ожидать, что переменная будет использоваться. Переменная может быть фундаментальным типом, таким как int, или сложным типом, таким как класс. Независимо от ее типа, переменная гарантированно будет существовать только столько времени, сколько говорит язык программирования.

C ++ управляет памятью совсем иначе, чем C #. С одной стороны, нет необходимости иметь сборщик мусора, и немногие реализации предоставляют его. В той степени, в которой реализации C ++ имеют автоматическое управление памятью, они в основном делают это с помощью интеллектуальных указателей и подсчета ссылок. Классы C ++ не живут автоматически в куче (управляемой GC или иным образом). Вместо этого они работают гораздо больше как структуры в C #.

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

C ++ дает вам больший контроль над управлением памятью, чем C #. Следствием этого является то, что язык C ++ и среда выполнения не могут сделать столько, чтобы предотвратить ошибочный код, как язык C # и CLR. Один из ключей к тому, чтобы стать хорошим программистом на C ++, — это понять, как работает управление памятью, и использовать лучшие практики для написания эффективного и корректного кода.


Глобальные переменные, в том числе внутри пространств имен, и переменные, помеченные статическим ключевым словом duration, имеют статическую длительность хранения.

Глобальные переменные инициализируются во время инициализации программы (т. Е. Период до того, как программа фактически начнет выполнение вашей функции main или wmain). Они инициализируются в том порядке, в котором они определены в исходном коде. Как правило, не стоит полагаться на порядок инициализации, поскольку рефакторинг и другие, казалось бы, невинные изменения могут легко привести к потенциальной ошибке в вашей программе.

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

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


Внутри блока объект имеет автоматическую длительность, если он определен без оператора new для его создания и без ключевого слова длительности хранения, хотя при желании он может иметь ключевое слово register. Это означает, что объект создается в момент, когда он определен, и уничтожается, когда программа выходит из блока, в котором объявлена ​​ее переменная, или когда ее переменной назначается новое значение.

Примечание . Раньше ключевое слово auto использовалось для явного выбора продолжительности автоматического хранения. В C ++ 11 это использование было удалено. Теперь это эквивалент ключевого слова var в C #. Если вы попытаетесь скомпилировать что-либо, используя старое значение auto, вы получите ошибку компилятора, так как auto как спецификатор типа должен быть единственным спецификатором типа.


Динамическая длительность является результатом использования оператора new или оператора new []. Оператор new используется для выделения отдельных объектов, а оператор new [] используется для выделения динамических массивов. Вы должны отслеживать размер динамически размещаемого массива. Хотя реализация C ++ должным образом освободит динамически размещенный массив, при условии, что вы используете оператор delete [], не существует простого или переносимого способа определения размера этого выделения. Это, вероятно, будет невозможно. Отдельные объекты освобождаются с помощью оператора удаления.

Когда вы выделяете память, используя new или new [], возвращаемое значение является указателем. Указатель — это переменная, которая содержит адрес памяти. В C #, если вы установите для всех ваших ссылок на объект значение null или какое-либо другое значение, то память больше не будет доступна в вашей программе, поэтому GC может освободить эту память для других целей.

В C ++, если вы установили для всех ваших указателей объект nullptr или другое значение, и вы не можете определить исходный адрес, используя арифметику указателей, то вы потеряли способность освобождать эту память с помощью операторов delete или delete []. , Тем самым вы создали утечку памяти. Если в программе происходит утечка достаточного объема памяти, в конечном итоге происходит сбой, поскольку в системе не хватает адресов памяти. Даже до этого, тем не менее, компьютер ужасно замедляется, так как вынужден увеличивать пейджинг, чтобы приспособиться к постоянно увеличивающемуся объему памяти вашей программы (при условии, что у него есть виртуальная память, которой нет в большинстве смартфонов).

Примечание: константный указатель, такой как someStr в выражении const wchar_t* someStr = L"Hello World!"; не является указателем динамической длительности. Эта память является лишь частью самой программы. Если вы попытаетесь вызвать delete или delete [] на нем, программа просто вылетит. Однако строка — это массив символов, поэтому, если ее можно было бы удалить, оператор delete [] был бы правильным для использования.

При работе с динамической памятью, чтобы устранить потенциальные утечки и ограничить возможность других связанных ошибок, вы всегда должны использовать умный указатель, такой как std::unique_ptr или std::shared_ptr . Мы обсудим это в главе, которая охватывает указатели.


Длительность потока — это наименее часто используемая продолжительность хранения. Это только недавно было стандартизировано. На момент написания этой статьи лишь немногие поставщики компиляторов C ++ реализовали поддержку нового ключевого слова thread_local из стандарта C ++ 11.

Это наверняка изменится, но сейчас вы можете использовать специфичные для поставщика расширения, такие как специфичное для Microsoft расширение _declspec (thread) или специфичное для GCC расширение __thread, если вам нужна функциональность такого рода.

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


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

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

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


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

Пример: StorageDurationSample \ SomeClass.h

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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;
};

Пример: StorageDurationSample \ SomeClass.cpp

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#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.»
        L»StringId: » << m_stringId.c_str() << L».»
        L»Address is: ‘0x» << setw(8) << setfill(L’0’) <<
        hex << addr << dec << L»‘.»
        L»Value is ‘» << m_value << L»‘.»
        L»Thread id: ‘» <<
        this_thread::get_id() << L»‘.»
}
 
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.»
        L»StringId: » << m_stringId.c_str() << L».»
        L»Address is: ‘0x» << setw(8) << setfill(L’0’) <<
        hex << addr << dec << L»‘.»
        L»Value is ‘» << m_value << L»‘.»
        L»Thread id: ‘» <<
        this_thread::get_id() << L»‘.»
}
 
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.»
        L»StringId: » << m_stringId.c_str() << L».»
        L»Address is: ‘0x» << setw(8) << setfill(L’0’) <<
        hex << addr << dec << L»‘.»
        L»Thread id: ‘» <<
        this_thread::get_id() << L»‘.»
}
 
// Note that when creating a static member variable, the definition also
// needs to have the type specified.
// ‘unique_ptr<SomeClass>’ before proceeding to the
// ‘SomeClass::s_someClass = …;’
unique_ptr<SomeClass> SomeClass::s_someClass =
    unique_ptr<SomeClass>(new SomeClass(10, L»s_someClass»));

Образец: StorageDurationSample \ StorageDurationSample.cpp

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#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.
    // 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.
    // ends or until a different value is assigned to it.
    // 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»‘.»
    Value::ThreadLocalSomeStruct.Value = value;
    wcout << L»New value is » << Value::ThreadLocalSomeStruct.Value <<
        L». Thread id: ‘» << this_thread::get_id() << L»‘.»
}
 
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.
    //// purpose of having a local static.
    //// 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»‘.»
}
 
int _pmain(int /*argc*/, _pchar* /*argv*/[])
{
    // Automatic storage;
    SomeClass sc1(1, L»_pmain sc1″);
    wcout << L»sc1 value: ‘» << sc1.GetValue() <<
        L»‘.»
    {
        // The braces here create a new block.
        // 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»‘.»
    }
 
    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.
    // 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.
    // 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.
    // of the PPLTasks API instead.
    thr.join();
 
    // Dynamic storage.
    // bad practice.
    // as an example.
    // 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.
    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.
    // arrInt are not initialized (ie 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»‘.»
 
        // 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»‘.»
    }
 
    // 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.
    //// use the array delete (ie 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.
    // ‘delete’ instead of ‘delete[]’ unknown bad things might happen.
    // implementations will overlook it while others would crash or do who
    // knows what else.
    delete[] p_arrInt;
    p_arrInt = nullptr;
 
    wcout << L»Ending program.»
    return 0;
}

Для тех, кому неудобно запускать образец, вот вывод, который я получаю, когда запускаю его из командной строки в Windows 8 Release Preview, скомпилированной с Visual Studio 2012 Ultimate RC в конфигурации отладки, предназначенной для набора микросхем x86. Вы, вероятно, создадите разные значения для адресов и идентификаторов потоков, если вы запустите его в своей собственной системе.

Продолжительность хранения — еще один важный аспект C ++, поэтому убедитесь, что вы хорошо понимаете, что мы обсуждали в этой статье, прежде чем двигаться дальше. Далее идут конструкторы, деструкторы и операторы.

Этот урок представляет собой главу из C ++ Succinctly , бесплатной книги от команды Syncfusion .