夹板在结构内关闭文件指针后涉嫌内存泄漏

splint alleged memory leak after closing file pointer within stucture

我正在尝试学习如何使用夹板,同时也在尝试重新学习 C。安全第一!

我有一个结构,里面有一个文件指针。文件指针在构造函数中打开,在析构函数中关闭。它在结构类型定义中用 /@only@/ 注释,夹板似乎认识到结构中的文件指针是指向该内存的唯一指针(详见下文)。

在析构函数中,只要文件指针不为空,文件就被关闭。

然而,splint 似乎在抱怨文件指针未被释放,导致内存泄漏,即使只要文件指针 != NULL 文件就关闭了。

代码如下:

#include <stdio.h>

struct FileStructure {
  /*@only@*/ FILE *file;
};

static /*@noreturn@*/ void die(const char *message)
{
  if ((bool) errno) {
    perror(message);
  } else {
    printf("ERROR: %s\n",message);
  }
  exit(EXIT_FAILURE);

}

static struct FileStructure *File_open(const char *filename)
{
  struct FileStructure *filestruct = malloc(sizeof(struct FileStructure));
  if(filestruct == NULL) die("Memory error");
  filestruct->file = fopen(filename,"r+");

  if(!filestruct->file) die("Failed to open the file");
  return filestruct;
}

static void File_close(/*@only@*/ struct FileStructure *filestruct)
{
  if(filestruct) {
    if(filestruct->file != NULL ) (void) fclose(filestruct->file);
    free(filestruct);
  }
}

int main(int argc, char *argv[])
{
  struct FileStructure *filestruct;
  char *filename;

  if(argc < 1) die("USAGE: program <filename>");

  filename=argv[1];
  filestruct=File_open(filename);
  File_close(filestruct);
  return 0;

}

这会导致以下错误:

so-splint-fclose.c: (in function File_open)
so-splint-fclose.c:22:3: Dependent storage assigned to only:
                            filestruct->file = fopen(filename, "r+")
  Dependent storage is transferred to a non-dependent reference. (Use
  -dependenttrans to inhibit warning)
so-splint-fclose.c: (in function File_close)
so-splint-fclose.c:32:10: Only storage filestruct->file (type FILE *) derived
    from released storage is not released (memory leak): filestruct
  A storage leak due to incomplete deallocation of a structure or deep pointer
  is suspected. Unshared storage that is reachable from a reference that is
  being deallocated has not yet been deallocated. Splint assumes when an object
  is passed as an out only void pointer that the outer object will be
  deallocated, but the inner objects will not. (Use -compdestroy to inhibit
  warning)

第二个错误:为什么 splint 认为 filestruct->file 没有在 File_close 中关闭,即使它已经通过 fclose

防止警告

首先,可以通过将 file 的声明更改为使用 /*@dependent@*/ 而不是 /*@only@*/ 注释来避免这两个警告:

struct FileStructure {
  /*@dependent@*/ FILE *file;
};

为什么这样可以防止错误

关于将 dependent 存储分配给 only 存储的第一个错误,这是因为 splint 使用的注释 fopen 函数包含一个 /*@dependent@*/ 声明中的注释,这与 FileStructure.

中定义 file/*@only@* 注释形成对比
# From splint's lib/standard.h
/*@null@*/ /*@dependent@*/ FILE *fopen (char *filename, char *mode) 

换句话说,splint 抱怨从 fopen 输出的 dependent 文件指针被分配给 only 变量。

解决这个问题也解决了第二个关于不从内部对象释放内存的错误 file 因为,如 splint manual 中指定:

It is up to the programmer to ensure that the lifetime of a dependent reference is contained within the lifetime of the corresponding owned reference." (Section 5.2.3)

为什么夹板认为有未释放的存储

至于实际问题,为什么 splint 认为有未释放的存储,考虑到 splint 使用的 free() 的注释声明,答案在 splint 的输出中:

Splint assumes when an object is passed as an out only void pointer that the outer object will be deallocated, but the inner objects will not.

"out only void pointer" 指的是 splint 使用的带注释的 free() 函数:

# From splint's lib/standard.h
void free( /*@null@*/ /*@out@*/ /*@only@*/ void *p ) /*@modified p@*/;

这意味着 filestruct 在输入到 free 时被视为 "out only void pointer",因此其内部对象被假定为未释放。

我很惊讶 splint 没有弄清楚内部对象在调用 free() 之前几行就被释放了,但也许这只是 splint 告诉我们使用 dependentowned 内部对象的注释。

参考文献:

  • 关于注释的更多细节 fopenfclose here.

  • splint manual(在 archive.org,因为 splint 网站 在撰写本文时已关闭)尤其参见第 5.2.3 节:拥有和相关参考文献。