TLS 的規則和限制
更新:2007 年 11 月
在宣告靜態繫結執行緒區域物件和變數時必須遵守下列方針:
thread 屬性只能套用於資料宣告和定義。它不可使用於函式宣告或定義。例如,下列程式碼會產生編譯器錯誤:
#define Thread __declspec( thread ) Thread void func(); // This will generate an error.
只能在具有 static 範圍的資料項目上指定 thread 修飾詞。這包含全域資料物件 (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 屬性不能用來當做型別修飾詞 (Type Modifier)。例如,下列程式碼會產生編譯器錯誤:
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;
因為允許使用 thread 屬性的 C++ 物件宣告,下列兩個範例在語義上是相同的:
#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++ 不允許這種執行緒資料的動態初始化。
如果 DLL 將任何非區域資料或物件宣告成 __declspec( thread ),如果動態載入,可能會造成保護錯誤。在 DLL 以 LoadLibrary 載入後,每當程式碼參考非區域 __declspec( thread ) 資料時會造成系統錯誤。因為執行緒的全域變數空間是在執行階段配置,這個空間的大小是根據應用程式的需求加上所有靜態連結的 DLL 的需求計算而來。當您使用 LoadLibrary 時,沒有方法來擴充這個空間以允許用 __declspec( thread ) 宣告的執行緒區域變數。如果 DLL 可以用 LoadLibrary 載入,使用 DLL 之中的 TLS API,例如 TlsAlloc,來配置 TLS。