Tcl包装器中的内存泄漏

时间:2014-05-11 20:47:22

标签: memory tcl

我在Tcl API中阅读了所有关于内存管理的内容,但到目前为止还无法解决我的问题。我写了一个Tcl扩展来访问现有的应用程序。它有效,除了一个严重的问题:内存泄漏。

我尝试使用最少的代码重现问题,您可以在帖子的末尾找到它。该扩展在命名空间 vtcl 中定义了一个新命令 recordings recordings 命令创建一个包含10000个元素的列表,每个元素都是一个新命令。每个命令都附有数据,这是录音的名称。每个命令的 name 子命令返回录制的名称。

我使用tclsh运行以下Tcl代码来重现问题:

load libvtcl.so
for {set ii 0} {$ii < 1000} {incr ii} {
  set recs [vtcl::recordings]
  foreach r $recs {rename $r ""}
}

foreach r $ recs {rename $ r“”} 删除每次迭代的所有命令,从而释放附加到每个命令的数据的内存(我可以看到GDB)。我还可以在gdb中看到变量 recs 的引用计数在每次迭代时变为0,以便释放列表的内容。尽管如此,我看到每次迭代时运行tclsh的进程的内存都在上升。

我不知道还能尝试什么。非常感谢帮助。

#include <stdio.h>
#include <string.h>
#include <tcl.h>

static void DecrementRefCount(ClientData cd);
static int ListRecordingsCmd(ClientData cd, Tcl_Interp *interp, int objc,
                             Tcl_Obj *CONST objv[]);
static int RecordingCmd(ClientData cd, Tcl_Interp *interp, int objc,
                        Tcl_Obj *CONST objv[]);

static void
DecrementRefCount(ClientData cd)
{
  Tcl_Obj *obj = (Tcl_Obj *) cd;
  Tcl_DecrRefCount(obj);
  return;
}

static int
ListRecordingsCmd(ClientData cd, Tcl_Interp *interp, int objc,
                  Tcl_Obj *CONST objv[])
{
  char name_buf[20];
  Tcl_Obj *rec_list = Tcl_NewListObj(0, NULL);

  for (int ii = 0; ii < 10000; ii++)
    {
      static int obj_id = 0;
      Tcl_Obj *cmd;
      Tcl_Obj *rec_name;

      cmd = Tcl_NewStringObj ("rec", -1);
      Tcl_AppendObjToObj (cmd, Tcl_NewIntObj (obj_id++));

      rec_name = Tcl_NewStringObj ("DM", -1);
      snprintf(name_buf, sizeof(name_buf), "%04d", ii);
      Tcl_AppendStringsToObj(rec_name, name_buf, (char *) NULL);
      Tcl_IncrRefCount(rec_name);

      Tcl_CreateObjCommand (interp, Tcl_GetString (cmd), RecordingCmd,
                            (ClientData) rec_name, DecrementRefCount);
      Tcl_ListObjAppendElement (interp, rec_list, cmd);
    }

  Tcl_SetObjResult (interp, rec_list);

  return TCL_OK;
}

static int
RecordingCmd(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  Tcl_Obj *rec_name = (Tcl_Obj *)cd;
  char *subcmd;

  subcmd = Tcl_GetString (objv[1]);
  if (strcmp (subcmd, "name") == 0)
    {
      Tcl_SetObjResult (interp, rec_name);
    } 

  else
    {
      Tcl_Obj *result = Tcl_NewStringObj ("", 0);
      Tcl_AppendStringsToObj (result,
                              "bad command \"",
                              Tcl_GetString (objv[1]),
                              "\"",
                              (char *) NULL);
      Tcl_SetObjResult (interp, result);
      return TCL_ERROR;
    }

  return TCL_OK;
}

int
Vtcl_Init(Tcl_Interp *interp)
{
#ifdef USE_TCL_STUBS
  if (Tcl_InitStubs(interp, "8.5", 0) == NULL) {
    return TCL_ERROR;
  }
#endif

  if (Tcl_PkgProvide(interp, "vtcl", "0.0.1") != TCL_OK)
    return TCL_ERROR;

  Tcl_CreateNamespace(interp, "vtcl", (ClientData) NULL,
                          (Tcl_NamespaceDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "::vtcl::recordings", ListRecordingsCmd,
                       (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

  return TCL_OK;
}

2 个答案:

答案 0 :(得分:3)

Tcl_Obj *引用计数的管理看起来绝对正确,但我确实想知道您是否释放了与实际代码中特定实例关联的所有其他资源。它也可能完全是另一回事;你的代码不是Tcl中唯一分配内存的东西!此外,Tcl中的默认内存分配器实际上并没有将内存返回给操作系统,而是保留它直到进程结束。找出问题可能很棘手。

您可以尝试将--enable-symbols=mem传递给configure来构建Tcl。这使得Tcl构建了一个额外的命令memory,它允许更广泛地检查内存管理行为(它还可以确保在释放内存后永远不会写入内存)。默认情况下它没有启用,因为它有很大的性能损失,但它可以帮助您追踪正在发生的事情。 (memory info子命令是从哪里开始的。)

您还可以尝试在构建时将-DPURIFY添加到CFLAGS;它完全禁用了Tcl内存分配器(因此内存检查工具,如 - commercial - Purify和--OSS - Electric Fence可以获得准确的信息,而不是被Tcl的高性能线程感知分配器弄得很困惑)并且可能让你弄明白发生了什么事。

答案 1 :(得分:1)

我发现泄漏的地方。在函数 ListRecordingsCmd 中,我替换了行

Tcl_AppendObjToObj (cmd, Tcl_NewIntObj (obj_id++));

Tcl_Obj *obj = Tcl_NewIntObj (obj_id++);
Tcl_AppendObjToObj (cmd, obj);
Tcl_DecrRefCount(obj);

未释放分配用于存储对象标识的内存。 tclsh进程使用的内存现在已稳定。