NAME

Tcl_NewObj, Tcl_DuplicateObj, Tcl_IncrRefCount, Tcl_DecrRefCount, Tcl_IsShared, Tcl_InvalidateStringRep - 操縱 Tcl 物件

總覽 SYNOPSIS

#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)

引數 ARGUMENTS

Tcl_Obj *objPtr (in)
指向一個物件;必須是以前呼叫 Tcl_NewObj 返回的結果。
    
 

介紹 INTRODUCTION

這個手冊頁提供了對 Tcl 物件以及如何使用它們的一個概述。它還描述了管理 Tcl 物件的一些一般過程。使用這些過程來建立和複製物件,和增加和減少到物件的引用(指標)計數。這些過程與那些在特定型別的物件如 Tcl_GetIntFromObjTcl_ListObjAppendElement 上進行操作的過程聯合使用。單獨的過程和它們所操縱的資料結構被放在一起描述。
Tcl 的雙埠( dual-ported)物件為儲存和交換 Tcl 值提供了一個通用的機制。它們在很大程度上替代了 Tcl 中字串的使用。例如,它們被用來儲存變數值、命令引數、命令結果、和指令碼。Tcl 物件外在表現很象字串,但它還持有可以被更加有效的操縱的內部表示。例如,現在一個 Tcl 列表被表示為持有列表的字串表示的一個物件,如同到每個列表元素的指標的一個數組。雙埠物件避免了執行時的型別轉換。它們還提高了許多操作的速度,原因是可以立即獲得一個適當的表示。編譯器自身使用 Tcl 物件來快取(cache)作為編譯指令碼的結果的位元組碼指令。
這兩種表示互為快取並且被以懶惰方式計算。就是說,每個表示都只在需要時才被計算,它被從另一種表示計算出來,而一旦被計算出來了,它就被儲存起來。除此之外,其中一個表示的改變將使另一個表示成為無效 。舉個例子,一個做整數運算的 Tcl 程式可以在一個變數的內部機器整數表示上進行直接操作,而不需要經常性的在整數和字串之間進行轉換。只有在需要這個變數的值的一個字串表示的時候,比如列印它,程式才重新生成這個整數的字串表示。儘管物件包含一個內部表示,但它們的語義仍是依據字串定義的: 總是可以獲取最新的字串,在取回物件的字串表示的時候,對物件的任何改變都將反映到取回的那個字串上。因為這個表示是無效的並被重新生成了,擴充套件作者直接訪問 Tcl_Obj 的欄位是很危險的。最好使用 Tcl_GetStringFromObjTcl_GetString 這樣的過程來訪問 Tcl_Obj 資訊。
在堆上分配物件,使用到它們的 Tcl_Obj 結構的指標引用物件。物件要儘可能的共享。這將顯著的縮減儲存需求,原因是一些物件比如長列表是非常大的。還有,多數 Tcl 值只是被讀而從不被修改。尤其是過程引數,它們可以在呼叫和被呼叫的過程之間共享。賦值和引數繫結是透過簡單的賦予到這個值的一個指標完成的。使用引用計數來確定什麼時候歸還一個物件的儲存是安全的。
Tcl 物件是有型別的(typed)。一個物件的內部表示由它自己的型別來控制。在 Tcl 核心中預定義了七種型別,其中包括:整數、雙精度浮點數、列表、和位元組碼。擴充套件作者可是使用 Tcl_RegisterObjType 過程來擴充套件型別的集合。
 

物件結構 THE TCL_OBJ STRUCTURE

每個 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;
byteslength 成員一起持有一個物件的字串表示,這是一個已計數的 ( counted) 字串或二進位制串 ( binary string),二進位制串可能包含有嵌入的 null 位元組的二進位制串。 bytes 指向這個字串表示的第一個位元組。 length 成員給出位元組數。位元組陣列的在偏移量 length 上,也就是最後一個位元組後面必須總是有一個 null;這允許不包含 null 的字串表示被作為一個常規的用 null 終結的 C 語言字串來對待。 C 程式使用 Tcl_GetStringFromObjTcl_GetString 來得到一個物件的字串表示。如果 bytes 是 NULL,則字串表示無效。
一個物件的型別管理它的內部表示。成員 typePtr 指向描述型別的 Tcl_ObjType 結構。如果 typePtr is 是 NULL,則內部表示無效。
internalRep 聯合成員持有一個物件的內部表示。它可以是一個(長)整數,一個雙精度浮點數,或者一個指標、它指向包含這個型別的物件要表示物件所需要的補充資訊的值,或者是兩個任意的指標。
使用 refCount 成員來通告在什麼時候釋放一個物件的儲存是安全的。它持有到這個物件的活躍引用的計數。維護正確的引用計數是擴充套件作者的一個關鍵性的責任。在下面的物件的儲存管理 ( STORAGE MANAGEMENT OF OBJECTS) 章節中討論了引用計數。
儘管擴充套件的作者可以直接訪問一個 Tcl_Obj 結構的成員,但最好還是使用恰當的過程和宏。例如,擴充套件作者永遠不要直接讀或修改 refCount;作為替代,他們應當使用象 Tcl_IncrRefCountTcl_IsShared 這樣的宏。
Tcl 物件的一個關鍵屬性是它持有兩個表示。典型的,一個物件開始時只包含一個字串表示: 它是無型別的並且 typePtr 是一個 NULL。分別使用 Tcl_NewObjTcl_NewStringObj 建立包含一個空串的一個物件或一個指定字串的一個復件。一個物件的字串值可以使用 Tcl_GetStringFromObjTcl_GetString 來獲取並使用 Tcl_SetStringObj 來改變它。如果如果這個物件以後被傳遞給象 Tcl_GetIntFromObj 這樣的要求一個特定的內部表示的過程,則這個過程將建立一個內部表示並設定這個物件的 typePtr。從字串表示來計算它的內部表示。一個物件的兩個表示是雙重的: 對一個的改變也將反映到另一個上。例如, Tcl_ListObjReplace 將修改一個物件的內部表示,下一個到 Tcl_GetStringFromObjTcl_GetString 的呼叫將反映這個改變。
出於效率的原因以懶惰方式重計算表示。一個過程如 Tcl_ListObjReplace 對一個表示的改變不立即反映到另一個表示上。作為替代,把另一個表示標記為無效,如果以後需要的話再重新生成。多數 C 程式設計師永遠無須關心這是如何完成的,他們只是簡單的使用象 Tcl_GetBooleanFromObjTcl_ListObjIndex 這樣的過程。而實現自己的物件型別的程式設計師必須檢查無效表示和在需要時標記一個表示為無效。使用過程 Tcl_InvalidateStringRep 來標記一個物件的字串表示為無效並釋放與這個字串表示相關聯的儲存。
物件在它的一生當中通常保持一種型別,但是有時一個物件必須從一種型別轉換成另一種型別。例如,一個 C 程式可以透過重複呼叫 Tcl_AppendToObj 來在一個物件中建造一個字串,並接著呼叫 Tcl_ListObjIndex 來從一個物件中提取一個列表元素。持有相同字串的同樣的物件在不同的時候可能有多種不同的內部表示。擴充套件作者可以使用 Tcl_ConvertToType 過程強制把一個物件從一種型別轉換成另一種型別。只有建立新物件型別的程式設計師才需要關心這是如何作的。作為物件型別實現的一部分,需要定義為一個物件建立一個新的內部表示和改變它 typePtr 的一個過程。如何建立一個新物件型別請參見 Tcl_RegisterObjType 手冊頁。
 

物件生命週期示例 EXAMPLE OF THE LIFETIME OF AN OBJECT

作為一個物件生命週期的一個例子,考慮下列命令序列:
set x 123
這裡把一個未知型別的物件賦值給 x,這個物件的 bytes 成員指向 123length 成員包含 3。物件的 typePtr 成員是 NULL。
puts "x is $x"
x 的字元表示是有效的(因為 bytes 是非 NULL)並被這個命令取回。
incr x
incr 命令首先透過呼叫 Tcl_GetIntFromObj 從 x (所引用的)的物件的得到一個整數。這個過程檢查這個物件是否已經是一個整數物件。由於它不是,就透過把這個物件的 internalRep.longValue 成員設定為整數 123,並把這個物件的 typePtr 設定為指向整數的 Tcl_ObjType 結構,此過程把這個物件轉換成了整數物件。兩個表示現在都是有效的。 incr 增加這個物件的整數內部表示,接著使它的字串表示無效(透過呼叫 Tcl_InvalidateStringRep),原因是這個字串表示不再與內部表示相對應了。
puts "x is now $x"
現在需要 x (所引用的)的物件的字串表示,要重新計算它。字串表示現在是 124。兩個表示又都是有效的了。
 

物件的儲存管理 STORAGE MANAGEMENT OF OBJECTS

Tcl 物件在堆上分配,並且要儘可能的共享物件來縮減儲存需求。使用引用計數來確定何時一個物件不再被需要並可以被安全的釋放。剛用 Tcl_NewObjTcl_NewStringObj 建立的物件的 refCount 是 0。當建立到這個物件的一個新引用時,使用宏 Tcl_IncrRefCount 增加引用計數。當不再需要一個引用的時候 ,使用 Tcl_DecrRefCount 減少引用計數,而且如果這個物件的引用計數下降到零,就釋放它的儲存。被不同的程式碼或資料結構共享的一個物件的 refCount 大於 1。增加一個物件的引用計數來確保它不會被過早釋放或者它的值被意外的改變。
舉個例子,位元組碼直譯器在呼叫者和被呼叫的過程之間共享引數物件,以避免複製物件。它把呼叫者的實際引數的物件賦值給過程的形式引數變數。此時,它呼叫 Tcl_IncrRefCount 來增加每個實際引數(所引用的)的物件的引用計數,原因是有了從形式引數到這個物件的一個新引用。在被呼叫的過程返回的時候,直譯器呼叫 Tcl_DecrRefCount 來減少每個引數的引用計數。當一個物件的引用下降到小於等於零的時候, Tcl_DecrRefCount 歸還它的儲存。多數命令過程不是必須關心引用計數的,原因是它們立即使用一個物件的值並且在它們返回之後不保留到這個物件的指標。但是,如果它們把到一個物件的指標保留到一個數據結構中,則他們必須注意要增加它的引用計數,原因是這個保留的指標是一個新引用。
lappendlinsert 這樣的直接修改物件的命令過程必須注意要在修改一個共享的物件之前複製它。 他們必須首先呼叫 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 的命令過程在增加變數(所引用的)物件內部表示中的整數之前,必須檢查這個變數(所引用的)物件是否是共享的。如果它是共享的,則需要複製這個物件,目的是避免意外的改變在其他資料結構中值。
 
 

參見 SEE ALSO

Tcl_ConvertToType, Tcl_GetIntFromObj, Tcl_ListObjAppendElement, Tcl_ListObjIndex, Tcl_ListObjReplace, Tcl_RegisterObjType
 

關鍵字 KEYWORDS

internal representation, object, object creation, object type, reference counting, string representation, type conversion
 

[中文版維護人]

寒蟬退士

[中文版最新更新]

2001/10/30

《中國 Linux 論壇 man 手冊頁翻譯計劃》:

http://cmpp.linuxforum.net

本頁面中文版由中文 man 手冊頁計劃提供。
 
中文 man 手冊頁計劃: https://github.com/man-pages-zh/manpages-zh