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 in ''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. ---- '''[DGP]'''