检查初始化程序和终结程序是否保证在块作用域中进行逻辑配对

时间:2016-12-09 19:37:27

标签: c static-analysis

是否存在任何用于分析C代码的自动方法或工具,以确定所有初始化函数调用是否与块作用域中的相应终结函数调用逻辑配对?

初始化程序和终结程序是指fopen()fclose()之类的对。

对原始问题进行了重大修改:删除了“代码路径”这一短语,并添加了“逻辑配对”和“在块范围内”的短语,以试图澄清原始问题意图。请参阅Eugene Sh。的答案下的广泛讨论。通过“逻辑配对”,我与文本配对形成鲜明对比,例如:源代码文本是否具有相同数量的初始化器和终结器调用并不重要。相反,当退出块作用域时(如果有的话),任何初始化函数调用都保证具有相应的终结函数调用。

实施例

确定:

initialize();
mystery_function_that_may_never_return();
finalize();

确定:

initialize();
if (cond)
   finalize();
else
   finalize();

不行:

initialize();
initialize();
finalize();

不行:

initialize();
finalize();
finalize();

不行:

initialize();
if (cond)
    finalize();
else
    return;

转移所管理的任何资源的最终责任/转移“所有权”也是有意义的,因此在这种情况下,检查将是所有初始化导致最终确定或所有权转移。

2 个答案:

答案 0 :(得分:3)

可能不是。

“在编译时确定X是否在运行时发生”或“在C中确定所有代码路径”的任何形式都会进入halting problem。也许你可以用Haskell等纯函数式语言来侥幸逃脱它。

例如......

bool find_thing( FILE *fp, const char* thing ) {
    ...go searching through the fp for thing...

    if( found_the_thing ) {
        fclose(fp);
        return true;
    }
    else {
        rewind(fp);
        return false;
    }
}

FILE *fp = fopen( filename, "r");
while( !find_thing(fp, that_thing) ) {
    ...mess with the file...
}

有点做作,它不在我的头顶,可能更适用于插座。 Point就是你的初始化和终结器不是线性序列,一旦它们被传递,添加到结构和数组,甚至可能被分配到全局,你就会遇到麻烦。

这是另一个例子。

typedef struct {
    FILE *fp;
    const char *filename;
} FileHandle;

FileHandle fh = malloc(sizeof(FileHandle));
fh->filename = file;
fh->fp = fopen(file, "r");

do_something(fh);

fclose(fh);
free(fh->filename);
free(fh);

看起来很简单!除了你怎么知道fh->fp包含与初始化的文件指针相同的文件指针?如果do_something改变了怎么办?怎么样它已经关闭呢?如果它开了一个新的怎么办?

这是另一个例子。

FILE *fps[10];

...initialize fps...

/* Returns a file pointer from fps */
FILE *fp = highest_priority(fps);

...do something with fp...

fclose(fp);

什么文件指针关闭?

正如评论中所提到的,在分配和释放内存方面可能更容易思考,这也有同样的问题。我们怎么知道fh->filename没有改变?如果它是NULL怎么办?如果它已被释放怎么办?如果fh已被释放怎么办?

部分工具只会给出琐碎的案例,或者会产生太多的误报或否定,以至于无用或者很烦人,你很快就会把它关掉。

如果您遇到这个问题很多,请考虑切换到一种在一个整洁的功能中为您处理所有IO操作的样式。传递一个函数指针,指示如何处理每一行。例如,脱离我的头顶......

int read_file( const char* filename, void *thunk,
               void(*handle_line)(const char *, void *));

您可以做的最好的事情是提供一个运行时,让您知道任何资源是否未被程序结束,或者最终确定两次,或者在未初始化时使用。这就是C中大多数内存检查器的工作方式,例如Valgrind

答案 1 :(得分:2)

正如@Schwern所提到的,这个问题可以归结为Fiddle

考虑“初始化”函数Start()和“终结器”函数Stop()

现在要解决程序P()的暂停问题,就可以在以下程序中运行建议的工具了:

Start();
P();
Stop();

并检查Start()是否与Stop()“配对”。

更正式:

假设有一个函数Paired(A),如果函数trueStart()之间存在代码路径,则返回Stop()(如果A中存在对于程序的任何给定输入。然后,对于任何程序P(),我们都可以构建一个程序P'() P'()由:

给出
Start();
P();
Stop();

Paired(P') = Halting(P)。因此,Paired Halting可以解决任何程序P的{​​{1}}。