Tcl_NewObj, Tcl_DuplicateObj, Tcl_IncrRefCount, Tcl_DecrRefCount, Tcl_IsShared,
Tcl_InvalidateStringRep - 操縱 Tcl 物件
#include <tcl.h>
Tcl_Obj *
Tcl_NewObj()
Tcl_Obj *
Tcl_DuplicateObj(objPtr)
Tcl_IncrRefCount(objPtr)
Tcl_DecrRefCount(objPtr)
int
Tcl_IsShared(objPtr)
Tcl_InvalidateStringRep(objPtr)
- Tcl_Obj *objPtr (in)
- 指向一個物件;必須是以前呼叫
Tcl_NewObj 返回的結果。
這個手冊頁提供了對 Tcl
物件以及如何使用它們的一個概述。它還描述了管理
Tcl
物件的一些一般過程。使用這些過程來建立和複製物件,和增加和減少到物件的引用(指標)計數。這些過程與那些在特定型別的物件如
Tcl_GetIntFromObj 和
Tcl_ListObjAppendElement
上進行操作的過程聯合使用。單獨的過程和它們所操縱的資料結構被放在一起描述。
Tcl 的雙埠(
dual-ported)物件為儲存和交換
Tcl
值提供了一個通用的機制。它們在很大程度上替代了
Tcl
中字串的使用。例如,它們被用來儲存變數值、命令引數、命令結果、和指令碼。Tcl
物件外在表現很象字串,但它還持有可以被更加有效的操縱的內部表示。例如,現在一個
Tcl
列表被表示為持有列表的字串表示的一個物件,如同到每個列表元素的指標的一個數組。雙埠物件避免了執行時的型別轉換。它們還提高了許多操作的速度,原因是可以立即獲得一個適當的表示。編譯器自身使用
Tcl
物件來快取(cache)作為編譯指令碼的結果的位元組碼指令。
這兩種表示互為快取並且被以懶惰方式計算。就是說,每個表示都只在需要時才被計算,它被從另一種表示計算出來,而一旦被計算出來了,它就被儲存起來。除此之外,其中一個表示的改變將使另一個表示成為無效
。舉個例子,一個做整數運算的
Tcl
程式可以在一個變數的內部機器整數表示上進行直接操作,而不需要經常性的在整數和字串之間進行轉換。只有在需要這個變數的值的一個字串表示的時候,比如列印它,程式才重新生成這個整數的字串表示。儘管物件包含一個內部表示,但它們的語義仍是依據字串定義的:
總是可以獲取最新的字串,在取回物件的字串表示的時候,對物件的任何改變都將反映到取回的那個字串上。因為這個表示是無效的並被重新生成了,擴充套件作者直接訪問
Tcl_Obj
的欄位是很危險的。最好使用
Tcl_GetStringFromObj 和
Tcl_GetString
這樣的過程來訪問
Tcl_Obj 資訊。
在堆上分配物件,使用到它們的
Tcl_Obj
結構的指標引用物件。物件要儘可能的共享。這將顯著的縮減儲存需求,原因是一些物件比如長列表是非常大的。還有,多數
Tcl
值只是被讀而從不被修改。尤其是過程引數,它們可以在呼叫和被呼叫的過程之間共享。賦值和引數繫結是透過簡單的賦予到這個值的一個指標完成的。使用引用計數來確定什麼時候歸還一個物件的儲存是安全的。
Tcl
物件是有型別的(typed)。一個物件的內部表示由它自己的型別來控制。在
Tcl
核心中預定義了七種型別,其中包括:整數、雙精度浮點數、列表、和位元組碼。擴充套件作者可是使用
Tcl_RegisterObjType
過程來擴充套件型別的集合。
每個 Tcl
物件都被表示為一個
Tcl_Obj
結構,其定義如下。
typedef struct Tcl_Obj {
int refCount;
char * bytes;
int length;
Tcl_ObjType * typePtr;
union {
long longValue;
double doubleValue;
VOID * otherValuePtr;
struct {
VOID * ptr1;
VOID * ptr2;
} twoPtrValue;
} internalRep;
} Tcl_Obj;
bytes 和
length
成員一起持有一個物件的字串表示,這是一個已計數的
(
counted)
字串或二進位制串 (
binary
string),二進位制串可能包含有嵌入的
null
位元組的二進位制串。
bytes
指向這個字串表示的第一個位元組。
length
成員給出位元組數。位元組陣列的在偏移量
length
上,也就是最後一個位元組後面必須總是有一個
null;這允許不包含 null
的字串表示被作為一個常規的用
null 終結的 C
語言字串來對待。 C
程式使用
Tcl_GetStringFromObj 和
Tcl_GetString
來得到一個物件的字串表示。如果
bytes 是
NULL,則字串表示無效。
一個物件的型別管理它的內部表示。成員
typePtr 指向描述型別的
Tcl_ObjType 結構。如果
typePtr is
是
NULL,則內部表示無效。
internalRep
聯合成員持有一個物件的內部表示。它可以是一個(長)整數,一個雙精度浮點數,或者一個指標、它指向包含這個型別的物件要表示物件所需要的補充資訊的值,或者是兩個任意的指標。
使用
refCount
成員來通告在什麼時候釋放一個物件的儲存是安全的。它持有到這個物件的活躍引用的計數。維護正確的引用計數是擴充套件作者的一個關鍵性的責任。在下面的物件的儲存管理
(
STORAGE MANAGEMENT OF OBJECTS)
章節中討論了引用計數。
儘管擴充套件的作者可以直接訪問一個
Tcl_Obj
結構的成員,但最好還是使用恰當的過程和宏。例如,擴充套件作者永遠不要直接讀或修改
refCount;作為替代,他們應當使用象
Tcl_IncrRefCount 和
Tcl_IsShared
這樣的宏。
Tcl
物件的一個關鍵屬性是它持有兩個表示。典型的,一個物件開始時只包含一個字串表示:
它是無型別的並且
typePtr 是一個
NULL。分別使用
Tcl_NewObj 或
Tcl_NewStringObj
建立包含一個空串的一個物件或一個指定字串的一個復件。一個物件的字串值可以使用
Tcl_GetStringFromObj 或
Tcl_GetString
來獲取並使用
Tcl_SetStringObj
來改變它。如果如果這個物件以後被傳遞給象
Tcl_GetIntFromObj
這樣的要求一個特定的內部表示的過程,則這個過程將建立一個內部表示並設定這個物件的
typePtr。從字串表示來計算它的內部表示。一個物件的兩個表示是雙重的:
對一個的改變也將反映到另一個上。例如,
Tcl_ListObjReplace
將修改一個物件的內部表示,下一個到
Tcl_GetStringFromObj 或
Tcl_GetString
的呼叫將反映這個改變。
出於效率的原因以懶惰方式重計算表示。一個過程如
Tcl_ListObjReplace
對一個表示的改變不立即反映到另一個表示上。作為替代,把另一個表示標記為無效,如果以後需要的話再重新生成。多數
C
程式設計師永遠無須關心這是如何完成的,他們只是簡單的使用象
Tcl_GetBooleanFromObj 或
Tcl_ListObjIndex
這樣的過程。而實現自己的物件型別的程式設計師必須檢查無效表示和在需要時標記一個表示為無效。使用過程
Tcl_InvalidateStringRep
來標記一個物件的字串表示為無效並釋放與這個字串表示相關聯的儲存。
物件在它的一生當中通常保持一種型別,但是有時一個物件必須從一種型別轉換成另一種型別。例如,一個
C
程式可以透過重複呼叫
Tcl_AppendToObj
來在一個物件中建造一個字串,並接著呼叫
Tcl_ListObjIndex
來從一個物件中提取一個列表元素。持有相同字串的同樣的物件在不同的時候可能有多種不同的內部表示。擴充套件作者可以使用
Tcl_ConvertToType
過程強制把一個物件從一種型別轉換成另一種型別。只有建立新物件型別的程式設計師才需要關心這是如何作的。作為物件型別實現的一部分,需要定義為一個物件建立一個新的內部表示和改變它
typePtr
的一個過程。如何建立一個新物件型別請參見
Tcl_RegisterObjType 手冊頁。
作為一個物件生命週期的一個例子,考慮下列命令序列:
這裡把一個未知型別的物件賦值給
x,這個物件的
bytes
成員指向
123 而
length
成員包含 3。物件的
typePtr 成員是 NULL。
x
的字元表示是有效的(因為
bytes 是非
NULL)並被這個命令取回。
incr 命令首先透過呼叫
Tcl_GetIntFromObj 從 x
(所引用的)的物件的得到一個整數。這個過程檢查這個物件是否已經是一個整數物件。由於它不是,就透過把這個物件的
internalRep.longValue
成員設定為整數
123,並把這個物件的
typePtr
設定為指向整數的 Tcl_ObjType
結構,此過程把這個物件轉換成了整數物件。兩個表示現在都是有效的。
incr
增加這個物件的整數內部表示,接著使它的字串表示無效(透過呼叫
Tcl_InvalidateStringRep),原因是這個字串表示不再與內部表示相對應了。
現在需要
x
(所引用的)的物件的字串表示,要重新計算它。字串表示現在是
124。兩個表示又都是有效的了。
Tcl
物件在堆上分配,並且要儘可能的共享物件來縮減儲存需求。使用引用計數來確定何時一個物件不再被需要並可以被安全的釋放。剛用
Tcl_NewObj 或
Tcl_NewStringObj
建立的物件的
refCount 是
0。當建立到這個物件的一個新引用時,使用宏
Tcl_IncrRefCount
增加引用計數。當不再需要一個引用的時候
,使用
Tcl_DecrRefCount
減少引用計數,而且如果這個物件的引用計數下降到零,就釋放它的儲存。被不同的程式碼或資料結構共享的一個物件的
refCount 大於
1。增加一個物件的引用計數來確保它不會被過早釋放或者它的值被意外的改變。
舉個例子,位元組碼直譯器在呼叫者和被呼叫的過程之間共享引數物件,以避免複製物件。它把呼叫者的實際引數的物件賦值給過程的形式引數變數。此時,它呼叫
Tcl_IncrRefCount
來增加每個實際引數(所引用的)的物件的引用計數,原因是有了從形式引數到這個物件的一個新引用。在被呼叫的過程返回的時候,直譯器呼叫
Tcl_DecrRefCount
來減少每個引數的引用計數。當一個物件的引用下降到小於等於零的時候,
Tcl_DecrRefCount
歸還它的儲存。多數命令過程不是必須關心引用計數的,原因是它們立即使用一個物件的值並且在它們返回之後不保留到這個物件的指標。但是,如果它們把到一個物件的指標保留到一個數據結構中,則他們必須注意要增加它的引用計數,原因是這個保留的指標是一個新引用。
象
lappend 和
linsert
這樣的直接修改物件的命令過程必須注意要在修改一個共享的物件之前複製它。
他們必須首先呼叫
Tcl_IsShared
來檢查這個物件是否是共享的。如果物件是共享的,則他們必須使用
Tcl_DuplicateObj
複製這個物件;它返回原始物件的一個新複製品,其
refCount 是
0。如果物件未被共享,則命令過程“擁有”這個物件並可以安全的直接修改它。例如,下列程式碼出現在實現
linsert
的命令過程當中。透過在
index 的前面插入
objc-3
新元素,這個過程修改在
objv[1]
中傳遞給它的列表物件
。
listPtr = objv[1];
if (Tcl_IsShared(listPtr)) {
listPtr = Tcl_DuplicateObj(listPtr);
}
result = Tcl_ListObjReplace(interp, listPtr, index, 0, (objc-3), &(objv[3]));
另一個例子,
incr
的命令過程在增加變數(所引用的)物件內部表示中的整數之前,必須檢查這個變數(所引用的)物件是否是共享的。如果它是共享的,則需要複製這個物件,目的是避免意外的改變在其他資料結構中值。
Tcl_ConvertToType, Tcl_GetIntFromObj, Tcl_ListObjAppendElement,
Tcl_ListObjIndex, Tcl_ListObjReplace, Tcl_RegisterObjType
internal representation, object, object creation, object type, reference
counting, string representation, type conversion
寒蟬退士
2001/10/30
http://cmpp.linuxforum.net
本頁面中文版由中文 man
手冊頁計劃提供。
中文 man 手冊頁計劃:
https://github.com/man-pages-zh/manpages-zh