Tcl offers several techniques that can be used to implement static data in command procedures. This page is intended to present the pros and cons. ---- Example: Here is a command procedure for a simple Tcl command that makes use of a "static" value. Roughly, it's a constant value that each invocation of the command will use without changing, but a value that need not be created at all if the command is never evaluated. int OneObjCommand(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_SetObjResult(interp, Tcl_NewIntObj(1)); return TCL_OK; } Tcl_CreateObjCommand(interp, "one", OneObjCommand, NULL, NULL); Note that each time [[one]] is evaluated, a new '''Tcl_Obj''' is created to hold the value '''1'''. Since Tcl is capable of managing shared '''Tcl_Obj'''s, other alternatives are possible that would set the result to be an additional reference to one shared '''Tcl_Obj''' rather than a new one each time. The alternatives differ mostly on where that shared '''Tcl_Obj''' is stored between calls. Why might we want an alternative? Most compelling reason is memory efficiency. Consider the script: for {set i 0} {$i < 1000000} {incr i} { set a($i) [one] } With the implementation of [[one]] above, 1 million '''Tcl_Obj''' structs have to be allocated. ---- '''Alternative 1: ClientData''' int OneObjCommand(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_SetObjResult(interp, (Tcl_Obj *)cd); return TCL_OK; } void OneDelete(ClientData cd) { Tcl_Obj *objPtr = (Tcl_Obj *)cd; Tcl_DecrRefCount(objPtr); } objPtr = Tcl_NewIntObj(1); Tcl_IncrObjCount(objPtr); Tcl_CreateObjCommand(interp, "one", OneObjCommand, (ClientData)objPtr, OneDelete); Here the shared '''Tcl_Obj''' with the value '''1''' is stuffed in the '''ClientData''' of the [[one]] command. One disadvantage of this alternative is that the shared '''Tcl_Obj''' is created whether or not [[one]] is ever called. For larger amounts of static data, that might be a waste worth avoiding. Another possible disadvantage might be conflict with other uses of the '''ClientData''' word that a command might have. ---- '''Alternative 2: One field in ClientData''' typedef struct OneData { Tcl_Obj *one; } OneData; int OneObjCommand(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { OneData *dataPtr = (OneData *)cd; if (dataPtr->one == NULL) { dataPtr->one = Tcl_NewIntObj(1); Tcl_IncrRefCount(dataPtr->one); } Tcl_SetObjResult(interp, dataPtr->one); return TCL_OK; } void OneDelete(ClientData cd) { OneData *dataPtr = (OneData *)cd; if (dataPtr->one != NULL) { Tcl_DecrRefCount(dataPtr->one); } Tcl_Free(dataPtr); } dataPtr = Tcl_Alloc((int)sizeof(OneData)); Tcl_CreateObjCommand(interp, "one", OneObjCommand, (ClientData)dataPtr, OneDelete); This is a good choice when the command is already making use of '''ClientData'''. ---- '''Alternative 3: Interp AssocData''' int OneObjCommand(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *objPtr = Tcl_GetAssocData(interp, "one", NULL); if (objPtr == NULL) { objPtr = Tcl_NewIntObj(1); Tcl_IncrRefCount(objPtr); Tcl_SetAssocData(interp, "one", OneAssocDelete, (ClientData)objPtr); } Tcl_SetObjResult(interp, objPtr); return TCL_OK; } void OneAssocDelete(ClientData cd, Tcl_Interp *interp) { Tcl_Obj *objPtr = (Tcl_Obj *)cd; Tcl_DecrRefCount(objPtr); } void OneDelete(ClientData cd) { Tcl_Interp *interp = (Tcl_Interp *)cd; Tcl_DeleteAssocData(interp, "one"); } Tcl_CreateObjCommand(interp, "one", OneObjCommand, (ClientData)interp, OneDelete); This alternative is best avoided on several grounds. First, it's vulnerable to a collision in the ''key'' name passed to the '''Tcl_*AssocData''' routines. Any other code that also wants to store data under the "one" key of this interp will interfere. Second, note that the '''ClientData''' of the command had to be set to ''interp'' just to enable '''OneDelete''' to call '''Tcl_DeleteAssocData'''. This ties up the '''ClientData''' and is a messy tangle for later code maintainers to understand. Third, it's likely that the performance cost of the '''Tcl_GetAssocData''' lookup (based on '''TCL_STRING_KEYS''') will be at least as expensive as the original '''Tcl_NewIntObj'''. This alternative could be simplified somewhat by dropping '''OneDelete''' and the command's '''ClientData'''. The price would be that the shared value would not be freed during command deletion, but would hang around until the entire interp is destroyed. ---- '''Alternative 4: ThreadData''' int OneObjCommand(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static Tcl_ThreadDataKey oneDatakey; Tcl_Obj **objPtrPtr = Tcl_GetThreadData( &oneDataKey, (int) sizeof (Tcl_Obj *)); if (*objPtrPtr == NULL) { *objPtrPtr = Tcl_NewIntObj(1); Tcl_IncrRefCount(*objPtrPtr); Tcl_CreateExitHandler(OneRelease, (ClientData)(*objPtrPtr)); } Tcl_SetObjResult(interp, *objPtrPtr); return TCL_OK; } void OneRelease(ClientData cd) { Tcl_Obj *objPtr = (Tcl_Obj *)cd; Tcl_DecrRefCount(objPtr); } Tcl_CreateObjCommand(interp, "one", OneObjCommand, NULL, NULL); In this alternative, the '''Tcl_Obj''' is shared by multiple interps in the same thread. For applications that use many interps in each thread, that can add up. In contrast to Alternative 3, the key value here is a '''Tcl_ThreadDataKey''' rather than a string, so it's much less likely to have any inadvertent collision in the key value with other code. Note, though, that after all [[one]] commands in all interps are deleted, the shared value will live on until the thread exits. One nice feature of this alternative is that the '''ClientData''' and '''Tcl_CmdDeleteProc''' arguments to '''Tcl_CreateObjCommand''' are both '''NULL'''. That is, the technique is implemented entirely within the command procedure; no special initialization is required. This also means there's no conflict with other uses the command may have for those arguments. This alternative could be modified to free the shared value when no more interps are using it (with the addition of a '''Tcl_CmdDeleteProc''' argument, etc.), but that would require storing in the ThreadData the set of interps currently sharing the shared value. For a single Tcl_Obj value, that additional overhead doesn't make sense, but the technique is available for cases where the shared, static data is more substantial, or when freeing an unused resource is otherwise of critical importance. ---- Comments on these, or other alternatives, are invited. '''[DGP]''' ---- For a [pure-Tcl] solution, see also [static variables]