Scroll to navigation

Tcl_Obj(3tcl) Tcl Library Procedures Tcl_Obj(3tcl)


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

8.0 Tcl