Спецификаторы класса хранения
Спецификаторы класса хранения являются частью decl-specifier-seq синтаксиса объявления имени. Вместе с областью видимости имени они управляют двумя независимыми его свойствами: длительностью хранения и связыванием.
Длительность хранения
Длительность хранения — это свойство объекта, которое определяет минимальное потенциальное время жизни хранилища, содержащего объект. Длительность хранения определяется конструкцией, используемой для создания объекта, и может быть одной из следующих:
- статическая длительность хранения
| (начиная с C++11) |
- автоматическая длительность хранения
- динамическая длительность хранения
Статическая, потоковая(начиная с C++11) и автоматическая длительности хранения связаны с объектами, вводимыми объявлениями, а также с временными объектами. Динамическая длительность хранения связана с объектами, созданными new-выражением, или с неявно созданными объектами.
Категории длительности хранения применяются и к ссылкам.
Длительность хранения подобъектов и ссылочных членов совпадает с длительностью хранения их полного объекта.
Спецификаторы
Следующие ключевые слова являются спецификаторами класса хранения:
| (до C++11) |
| (до C++17) |
- static
| (начиная с C++11) |
- extern
- mutable
В decl-specifier-seq может быть не более одного спецификатора класса хранения, за исключением того, что thread_local может употребляться вместе с static или extern(начиная с C++11).
mutable не влияет на длительность хранения. Информацию об использовании этого спецификатора см. в разделе const/volatile.
Другие спецификаторы класса хранения могут присутствовать в decl-specifier-seq следующих объявлений:
| Спецификатор | Может присутствовать в decl-specifier-seq следующих объявлений | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Объявления переменных | Объявления функций | Объявления структурированной привязки (начиная с C++17) |
|||||||
| Не являющиеся членами | Являющиеся членами | Не являющиеся членами | Являющиеся членами | ||||||
| Не являющиеся параметрами | Параметры функции | Нестатические | Статические | Нестатические | Статические | ||||
| auto | Только блочная область видимости | Да | Нет | Нет | Нет | Нет | Нет | Н/Д | |
| register | Только блочная область видимости | Да | Нет | Нет | Нет | Нет | Нет | Н/Д | |
| static | Да | Нет | Объявляет статическую переменную | Только область видимости пространства имён | Объявляет статическую функцию | Да | |||
| thread_local | Да | Нет | Нет | Да | Нет | Нет | Нет | Да | |
| extern | Да | Нет | Нет | Нет | Да | Нет | Нет | Нет | |
Анонимные объединения также можно объявлять со спецификатором static.
| register — это подсказка о том, что объявленная таким образом переменная будет часто использоваться, поэтому её значение можно хранить в регистре процессора. Подсказка может быть проигнорирована; в большинстве реализаций она игнорируется, если берётся адрес переменной. Такое использование устарело. | (до C++17) |
Статическая длительность хранения
Переменная, удовлетворяющая всем следующим условиям, имеет статическую длительность хранения:
- Она принадлежит области видимости пространства имён или впервые объявлена со спецификатором static либо extern.
| (начиная с C++11) |
Хранилище этих сущностей существует в течение всей программы.
Длительность хранения потокаВсе переменные, объявленные со спецификатором thread_local, имеют длительность хранения потока. Хранилище этих сущностей существует в течение всего потока, в котором они созданы. Для каждого потока существует отдельный объект или ссылка, и при использовании объявленного имени имеется в виду сущность, связанная с текущим потоком. | (начиная с C++11) |
Автоматическая длительность хранения
Следующие переменные имеют автоматическую длительность хранения:
- Переменные, принадлежащие блочной области видимости и не объявленные явно со спецификатором static, thread_local(начиная с C++11) или extern. Хранилище таких переменных существует до выхода из блока, в котором они созданы.
- Переменные, принадлежащие области видимости параметров (то есть параметры функций). Хранилище параметра функции существует до момента сразу после его уничтожения.
Кроме того, временные объекты также имеют автоматическую длительность хранения.
Динамическая длительность хранения
Объекты, созданные следующими способами во время выполнения программы, имеют динамическую длительность хранения:
- new-выражения. Хранилище таких объектов выделяется функциями выделения памяти и освобождается функциями освобождения памяти.
- Неявное создание другими способами. Хранилище таких объектов перекрывается с некоторым существующим хранилищем.
- Объекты исключений. Хранилище таких объектов выделяется и освобождается способом, не определённым спецификацией.
Связывание
Имя может иметь внешнее связывание, связывание с модулем(начиная с C++20), внутреннее связывание или не иметь связывания:
- Сущность, имя которой имеет внешнее связывание, может быть объявлена повторно в другой единице трансляции; повторное объявление может быть привязано к другому модулю(начиная с C++20).
| (начиная с C++20) |
- Сущность, имя которой имеет внутреннее связывание, может быть повторно объявлена в другой области видимости той же единицы трансляции.
- Сущность, имя которой не имеет связывания, может быть повторно объявлена только в той же области видимости.
Различают следующие виды связывания:
Отсутствие связывания
Любые из следующих имён, объявленные в блочной области видимости, не имеют связывания:
- переменные, не объявленные явно со спецификатором extern (независимо от наличия спецификатора static);
- локальные классы и их функции-члены;
- другие имена, объявленные в блочной области видимости, например псевдонимы типов, перечисления и перечислители.
Имена, для которых не указано внешнее, связывание с модулем(начиная с C++20) или внутреннее связывание, также не имеют связывания независимо от области видимости, в которой они объявлены.
Внутреннее связывание
Любые из следующих имён, объявленные в области видимости пространства имён, имеют внутреннее связывание:
- переменные, шаблоны переменных(начиная с C++14), функции или шаблоны функций, объявленные со спецификатором static;
- нешаблонные (начиная с C++14)переменные с типом const-квалифицированным и не volatile-квалифицированным, за исключением случаев, когда
| (начиная с C++17) |
| (начиная с C++20) |
- они явно объявлены со спецификатором extern или
- они были объявлены ранее, и предыдущее объявление не имело внутреннего связывания;
- члены данных анонимных объединений.
| Кроме того, все имена, объявленные в безымянных пространствах имён или в пространстве имён, вложенном в безымянное пространство имён, имеют внутреннее связывание, даже если они явно объявлены со спецификатором extern. | (начиная с C++11) |
Внешнее связывание
Переменные и функции с внешним связыванием также имеют языковое связывание, позволяющее связывать единицы трансляции, написанные на разных языках программирования.
Любые из следующих имён, объявленные в области видимости пространства имён, имеют внешнее связывание, если они не объявлены в безымянном пространстве имён и их объявления не привязаны к именованному модулю либо не экспортированы(начиная с C++20):
- переменные и функции, не перечисленные выше (то есть функции, не объявленные со спецификатором static, неконстантные переменные, не объявленные со спецификатором static, а также любые переменные, объявленные со спецификатором extern);
- перечисления;
- имена классов, их функций-членов, статических членов данных (константных или нет), вложенных классов и перечислений, а также функций, впервые объявленных с помощью объявлений friend внутри тела класса;
- имена всех шаблонов, не перечисленных выше (то есть не являющихся шаблонами функций, объявленными со спецификатором static).
Любые из следующих имён, впервые объявленные в блочной области видимости, имеют внешнее связывание:
- имена переменных, объявленных со спецификатором extern;
- имена функций.
Связывание с модулемИмена, объявленные в области видимости пространства имён, имеют связывание с модулем, если их объявления привязаны к именованному модулю, не экспортированы и не имеют внутреннего связывания. | (начиная с C++20) |
Статические переменные блочной области видимости
Переменные блочной области видимости со статической или потоковой(начиная с C++11) длительностью хранения инициализируются при первом прохождении управления через их объявление (если только их инициализация не является нулевой или константной; такая инициализация может быть выполнена до первого входа в блок). При всех последующих вызовах объявление пропускается.
- Если при инициализации выбрасывается исключение, переменная не считается инициализированной, и при следующем прохождении управления через объявление будет предпринята новая попытка инициализации.
- Если в процессе инициализации переменной происходит рекурсивный вход в блок, в котором она инициализируется, поведение не определено.
| (начиная с C++11) |
Деструктор переменной блочной области видимости со статической длительностью хранения вызывается при завершении программы, но только если инициализация успешно состоялась.
Переменные со статической длительностью хранения во всех определениях одной и той же inline-функции (которая может быть неявно inline) ссылаются на один и тот же объект, определённый в одной единице трансляции, если функция имеет внешнее связывание.
Сущности, локальные для единицы трансляции
Понятие сущностей, локальных для единицы трансляции, стандартизировано в C++20. Подробнее см. эту страницу.
Сущность является локальной для единицы трансляции (или, сокращённо, TU-local), если
- она имеет имя с внутренним связыванием или
- она не имеет имени со связыванием и вводится в определении сущности, локальной для единицы трансляции, или
- она является шаблоном или специализацией шаблона, аргумент шаблона или объявление шаблона которого использует сущность, локальную для единицы трансляции.
Могут возникнуть нежелательные последствия (обычно нарушение ODR), если тип сущности, не являющейся локальной для единицы трансляции, зависит от сущности, локальной для единицы трансляции, или если объявление либо направляющая вывода для(начиная с C++17) сущности, не являющейся локальной для единицы трансляции, ссылается на сущность, локальную для единицы трансляции, вне следующих контекстов:
- тело функции, не являющейся inline, или шаблона функции;
- инициализатор переменной или шаблона переменной;
- объявления friend в определении класса;
- использование значения переменной, если переменная может использоваться в константных выражениях
| Такое использование запрещено в единице интерфейса модуля (за пределами её закрытого фрагмента модуля, если он есть) или разделе модуля и считается устаревшим в любом другом контексте. Объявление в одной единице трансляции не может ссылаться на сущность, локальную для другой единицы трансляции, если эта единица не является заголовочной единицей. Объявление, инстанцированное для шаблона, появляется в точке инстанцирования специализации. | (начиная с C++20) |
Примечания
Имена в области видимости пространства имён верхнего уровня (области видимости файла в C), которые имеют спецификатор const и не имеют спецификатора extern, обладают внешним связыванием в C, но внутренним связыванием в C++.
Начиная с C++11, auto больше не является спецификатором класса хранения; он используется для указания вывода типа.
| В C нельзя получить адрес переменной register, но в C++ переменная, объявленная со спецификатором register, семантически неотличима от переменной, объявленной без каких-либо спецификаторов класса хранения. | (до C++17) |
| В C++, в отличие от C, переменные нельзя объявлять со спецификатором register. | (начиная с C++17) |
Имена переменных thread_local с внутренним или внешним связыванием, на которые ссылаются из разных областей видимости, могут обозначать один и тот же экземпляр или разные экземпляры — в зависимости от того, выполняется ли код в одном или разных потоках.
Ключевое слово extern также может использоваться для указания языкового связывания и объявлений явного инстанцирования шаблонов, но в этих случаях оно не является спецификатором класса хранения (за исключением случая, когда объявление непосредственно содержится в спецификации языкового связывания; тогда объявление рассматривается так, как если бы оно содержало спецификатор extern).
Спецификаторы класса хранения, кроме thread_local, запрещены в явных специализациях и явных инстанцированиях:
template<class T>
struct S
{
thread_local static int tlm;
};
template<>
thread_local int S<float>::tlm = 0; // "static" does not appear here
| Раньше шаблон переменной со спецификатором const (который может подразумеваться спецификатором constexpr) по умолчанию имел внутреннее связывание, что не соответствовало поведению других сущностей-шаблонов. Это исправлено отчётом об ошибке CWG2387. | (начиная с C++14) |
inline служит обходным решением для CWG2387, обеспечивая внешнее связывание по умолчанию. Именно поэтому спецификатор inline был добавлен во многие шаблоны переменных, а затем удалён после принятия CWG2387. Реализациям стандартной библиотеки также необходимо использовать inline, пока поддерживаемый компилятор не реализовал CWG2387. См. GCC Bugzilla #109126 и MSVC STL PR #4546. | (начиная с C++17) |
| Макрос проверки поддержки возможности | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_threadsafe_static_init | 200806L | (C++11) | Динамическая инициализация и уничтожение с поддержкой параллелизма |
Ключевые слова
auto, register, static, extern, thread_local, mutable
Пример
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
thread_local unsigned int rage = 1;
std::mutex cout_mutex;
void increase_rage(const std::string& thread_name)
{
++rage; // modifying outside a lock is okay; this is a thread-local variable
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';
}
int main()
{
std::thread a(increase_rage, "a"), b(increase_rage, "b");
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Rage counter for main: " << rage << '\n';
}
a.join();
b.join();
}
Возможный вывод:
Rage counter for a: 2 Rage counter for main: 1 Rage counter for b: 2
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применён к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 216 | C++98 | безымянный класс и перечисление в области видимости класса имеют связывание, отличное от связывания в области видимости пространства имён | во всех этих областях видимости они имеют внешнее связывание |
| CWG 389 | C++98 | имя без связывания не должно использоваться для объявления сущности со связыванием | тип без связывания не должен использоваться как тип переменной или функции со связыванием, если только переменная или функция не имеет языкового связывания C |
| CWG 426 | C++98 | сущность могла быть объявлена одновременно с внутренним и внешним связыванием в одной единице трансляции | в этом случае программа некорректна |
| CWG 527 | C++98 | ограничение на тип, введённое решением CWG 389, распространялось также на переменные и функции, которые нельзя именовать за пределами собственных единиц трансляции | ограничение снято для этих переменных и функций (то есть для сущностей без связывания или с внутренним связыванием, а также объявленных в безымянных пространствах имён) |
| CWG 809 | C++98 | спецификатор register практически не выполнял никаких функций | устарел |
| CWG 1648 | C++11 | static подразумевался, даже если thread_local использовался вместе с extern | подразумевается только при отсутствии других спецификаторов класса хранения |
| CWG 1686 | C++98 C++11 | имя нестатической переменной, объявленной в области видимости пространства имён, имело внутреннее связывание только в том случае, если она явно объявлена как const (C++98) или constexpr (C++11) | требовалось лишь, чтобы тип имел квалификатор const |
| CWG 2019 | C++98 | длительность хранения ссылочных членов не была определена | совпадает с длительностью хранения полного объекта |
| CWG 2387 | C++14 | неясно, имеет ли шаблон переменной с квалификатором const внутреннее связывание по умолчанию | квалификатор const не влияет на связывание шаблонов переменных и их экземпляров |
| CWG 2533 | C++98 | длительность хранения неявно созданных объектов была неясна | уточнено |
| CWG 2850 | C++98 | было неясно, когда освобождается хранилище параметров функций | уточнено |
| CWG 2872 | C++98 | значение выражения «может быть упомянуто» было неясно | формулировка уточнена |
| P2788R0 | C++20 | объявление переменной с квалификатором const в пространстве имён придавало ей внутреннее связывание даже в модуле | внутреннее связывание не предоставляется |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
- 6.7.5 Длительность хранения [basic.stc]
- Стандарт C++20 (ISO/IEC 14882:2020):
- 6.7.5 Длительность хранения [basic.stc]
- Стандарт C++17 (ISO/IEC 14882:2017):
- 6.7 Длительность хранения [basic.stc]
- Стандарт C++14 (ISO/IEC 14882:2014):
- 3.7 Длительность хранения [basic.stc]
- Стандарт C++11 (ISO/IEC 14882:2011):
- 3.7 Длительность хранения [basic.stc]
- Стандарт C++03 (ISO/IEC 14882:2003):
- 3.7 Длительность хранения [basic.stc]
- Стандарт C++98 (ISO/IEC 14882:1998):
- 3.7 Длительность хранения [basic.stc]
См. также
| Документация по C о длительности хранения |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/storage_duration