10
17
2014
2

(lib)Tcl 现代方法:变量

之前有人问到如何从 C 端访问 Tcl 变量,此处做一总结。庶几不失「现代」之名。有关 Tcl_Obj 的维护者资料击此

于是终于回到了主线。《Tcl 现代方法》本来是作为记录 Tcl 现代用法的系列博文,一开始是介绍 C 库的,但是后来 Tcl 脚本的内容反倒更多,结果现在纯粹谈起来 C 库又会导致歧义,因此在标题前加上 lib 标记,以示内容是 C 库而非 Tcl 脚本。

背景

Tcl 自 8.0 起,引入了 Tcl_Obj 系统,该系统在保留 Tcl 「Everything is a String」原则的同时,大大提高了程序效率,可以总结成这样:Tcl 变量存在一个显类型(Tcl 端类型)和隐类型(C 端类型),程序员脑中又有一个实用类型,显类型一定是字符串,但实际上实用类型并不一定是显类型,而使用隐类型的一种或多种,如 for 循环中的计数器 i,实用整数类型,而显类型则是字符串,隐类型应当是 int。

在过去,显类型和隐类型都是字符串,导致计数器 i 每次循环都会走一遍「字符串->整数->字符串」,效率极差。Tcl_Obj 则将字符串表示和隐类型数据一起保存,每当需要字符串时,就直接返回字符串,需要隐类型数据时,就直接返回隐类型数据;如果两者不匹配,则将其中过期了的一种更新。如每次 for 循环中如果有 puts $i,则会每次都更新一次 i 的字符串表示然后打印;如果在 for 循环外执行 puts $i,则只会造成一次字符串表示更新。更新隐类型数据同理。

说来好玩,因为一开始没用 Tcl_Obj,所以 Tcl 有一套相当强大的字符串处理函数,叫做 Tcl_DString(dynamic string),因为不推荐用于变量,而且比较直观,一看就懂,故而此处不做讨论。

另外注意,我提到的隐类型、显类型、实用类型,都是为了讨论方便自己提出的,实际上 Tcl 界乃至编程界并没有这样的说法(或许会有类似的讲法,如果你知道请务必告诉我)。

看上去好像很复杂,其实规则很简单:dual-ported (internal representation and string representation); cache of each other and computed lazily.

不过絮叨一大串,其实本文不介绍 Tcl_Obj 用法,只是个背景而已。

示例代码

A piece of code is worth a thousand words.

#include <tcl.h>

int z = 0;

void ObjRW(Tcl_Interp *interp)
{
    Tcl_Obj *xObj;
    double val;

    /* Read */
    xObj = Tcl_GetVar2Ex(interp, "x", NULL, 0); /* not incr'ing its refcount */
    Tcl_GetDoubleFromObj(interp, xObj, &val); /* not incr'ing its refcount */
    printf("x = %lf\n", val);
    /* Write */
    Tcl_SetDoubleObj(xObj, 6.28);
    xObj = Tcl_SetVar2Ex(interp, "x", NULL, xObj, 0); /* not incr'ing its refcount */
    /* Read (again) */
    Tcl_GetDoubleFromObj(interp, xObj, &val);
    Tcl_DecrRefCount(xObj);
    printf("x = %lf (new)\n", val);
}

/* Inefficient in most cases. Use Tcl_Obj instead. */
void StrRW(Tcl_Interp *interp)
{
    /* the string representation of y will be updated and returned */
    printf("y = %s\n", Tcl_GetVar(interp, "y", 0));
    Tcl_SetVar(interp, "y", "42", 0); /* now the "real" data will be outdated */
    printf("y = %s (new)\n", Tcl_GetVar(interp, "y", 0));
}

void LinkedVar(Tcl_Interp *interp)
{
    Tcl_Obj *zObj;
    int value;

    /* Original value */
    zObj = Tcl_GetVar2Ex(interp, "z", NULL, 0); /* Tcl simply returns our z */
    Tcl_GetIntFromObj(interp, zObj, &value);
    printf("z = %d\n", value);
    Tcl_DecrRefCount(zObj); /* delete the old obj */
    /* C-side */
    z = 10;
    zObj = Tcl_GetVar2Ex(interp, "z", NULL, 0); /* not incr'ing its refcount */
    Tcl_GetIntFromObj(interp, zObj, &value);
    printf("z = %d (C new)\n", value);
    /* Tcl-side */
    Tcl_SetIntObj(zObj, 100);
    Tcl_SetVar2Ex(interp, "z", NULL, zObj, 0); /* update the var */
    Tcl_DecrRefCount(zObj);
    printf("z = %d (Tcl new)\n", z); /* our own z got updated */
}

int main()
{
    Tcl_Interp *interp;
    Tcl_Obj *var1, *var2; /* var1 double, var2 int */

    /* Initialize */
    interp = Tcl_CreateInterp();
    /* Create Tcl_Obj's */
    var1 = Tcl_NewDoubleObj(3.14);
    var2 = Tcl_NewIntObj(99);
    /* Create variables */
    /* Not incrementing the refcount means we transfer the ownership */
    Tcl_SetVar2Ex(interp, "x", NULL, var1, 0); /* NULL means x isn't an array */
    Tcl_SetVar2Ex(interp, "y", NULL, var2, 0);
    Tcl_LinkVar(interp, "z", (void *)&z, TCL_LINK_INT); /* MAGIC! */
    /* Read/write them */
    ObjRW(interp);
    StrRW(interp);
    LinkedVar(interp);
    /* Finalize */
    Tcl_UnsetVar(interp, "x", 0);
    Tcl_UnsetVar(interp, "y", 0);
    Tcl_UnsetVar(interp, "z", 0);
    Tcl_DeleteInterp(interp);
    return 0;
}

参考文献

(写这么一节参考文献,应该能让我的博客看上去更专业吧smiley

  1. Tcl Source Code Wiki
  2. Tcl man pages
  3. 《Tcl/Tk 入门经典》,中文版第二版
Category: 编程 | Tags: tcl tcl 现代方法 | Read Count: 2277
Avatar_small
rca 说:
Oct 23, 2014 08:56:57 AM

这么古老的东西,好玩么

Avatar_small
Mike Manilone 说:
Oct 25, 2014 10:27:05 AM

@rca: 很有意思的,和 Lisp 的可玩性差不多。不过毕竟确实老了些,没有闭包,但是其他东西都很全。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

Powered by Chito | Hosted at is-Programmer | Theme: Aeros 2.0 by TheBuckmaker.com