Правила и ограничения протокола TLS
При объявлении статистически связанных локальных объектов потока и переменных необходимо придерживаться следующих рекомендаций:
Атрибут thread может применяться только в объявлениях и определениях данных. Его невозможно использовать в объявлениях или определениях функций. Например, следующий код вызовет ошибку компиляции:
#define Thread __declspec( thread ) Thread void func(); // This will generate an error.
Модификатор thread может указываться только в элементах данных статической области памяти (static). К ним относятся глобальные объекты данных (static и extern), локальные статические объекты и статические члены данных в классах C++. Автоматические объекты данных не могут объявляться с атрибутом thread. Следующий код создает ошибки компилятора:
#define Thread __declspec( thread ) void func1() { Thread int tls_i; // This will generate an error. } int func2( Thread int tls_i ) // This will generate an error. { return tls_i; }
В объявлениях и в определении локального объекта потока должен указываться атрибут thread. Например, следующий код вызывает ошибку:
#define Thread __declspec( thread ) extern int tls_i; // This will generate an error, since the int Thread tls_i; // declaration and definition differ.
Атрибут thread нельзя использовать в качестве модификатора типа. Например, следующий код вызовет ошибку компиляции:
char __declspec( thread ) *ch; // Error
Классы C++ не могут использовать атрибут thread. При этом возможно создание экземпляров объектов классов C++ с атрибутом thread. Например, следующий код вызовет ошибку компиляции:
#define Thread __declspec( thread ) class Thread C // Error: classes cannot be declared Thread. { // Code }; C CObject;
Поскольку объявление объектов C++, в которых используется атрибут thread, разрешено, следующие два примера семантически равнозначны.
#define Thread __declspec( thread ) Thread class B { // Code } BObject; // OK--BObject is declared thread local. class B { // Code }; Thread B BObject; // OK--BObject is declared thread local.
Адрес локального объекта потока не является постоянным, и любое выражение с таким адресом не является константным. В стандартном языке C результатом этого является запрет на использование адреса локальной переменной потока в качестве инициализатора объекта или указателя. Например, следующий код будет помечен компилятором C как приводящим к ошибке:
#define Thread __declspec( thread ) Thread int tls_i; int *p = &tls_i; //This will generate an error in C.
Это ограничение не применяется в C++. Поскольку в C++ разрешена динамическая инициализация всех объектов, то можно инициализировать объект с помощью выражения, в котором используется адрес локальной переменной потока. Это выполняется так же, как и построение локальных объектов потока. Например, вышеприведенный код не вызовет ошибку, если он компилируется как исходный файл C++. Обратите внимание, что адрес локальной переменной потока является допустимым, если поток, в котором этот адрес используется, все ещё существует.
В стандартном языке C разрешена инициализация объекта или переменной с помощью выражения, предусматривающего ссылку на себя, но только для объектов нестатической области памяти. Хотя в C++ обычно разрешена такая динамическая инициализация объектов выражениями с ссылкой на себя, такой тип инициализации не допускается в отношении локальных объектов потока. Примеры.
#define Thread __declspec( thread ) Thread int tls_i = tls_i; // Error in C and C++ int j = j; // OK in C++, error in C Thread int tls_i = sizeof( tls_i ) // Legal in C and C++
Обратите внимание, что выражение sizeof, включающее инициализируемый объект, не является ссылкой на себя и является допустимым как в C, так и в C++.
В C++ такая динамическая инициализация данных потока не разрешена, так как в дальнейшем локальная память потока будет усовершенствована.
В операционных системах Windows до Windows Vista ключевое слово __declspec(thread) имеет некоторые ограничения. Если библиотека DLL объявляет любые данные или объекты как __declspec(поток), может возникнуть сбой защиты при динамической загрузке этих данных или объектов. После загрузки библиотеки DLL с помощью функции LoadLibrary сбой в системе происходит всякий раз, когда код ссылается на данные __declspec(поток). Пространство глобальных переменных для потока выделяется во время выполнения, поэтому размер данного пространства определяется исходя из расчетов требований приложения, а также требований всех статически компонуемых библиотек DLL. При использовании функции LoadLibrary не существует способа расширения этого пространства, чтобы разрешить объявление локальных переменных потока с ключевым словом __declspec (thread). В библиотеках DLL следует использовать API-функции TLS, такие как TlsAlloc, чтобы разместить TLS, если библиотека DLL загружается с помощью функции LoadLibrary.