C 中的清除函数

Cleanup function in C

我有一个 C 程序,它分配了一些缓冲区,需要在程序退出执行之前清除这些缓冲区。我的 main 程序看起来像这样

int main(int argc, char** argv){
    // Some code
    // ...
    // ...
    void* data1 = malloc(someSize);
    void* data2 = malloc(someSize);
    double* result = malloc(resultSize);
    double* buffer = malloc(buffSize);
    // Some code
    // ...
    // First exit point
    if(someExitcondition){
        // free all allocated memory
        exit(1);
    }
    // Some code
    // ...
    // Second exit point
    if(someOtherExitcondition){
        // free all allocated memory
        exit(1);
    }
    // Some code
    // ...

    // free all allocated memory
    return 0;
}

我想通过调用 cleanUp() 函数来简化清理工作,该函数将 free 在堆中分配的所有内存。我想在每个 exit(1) 调用之前和 return 0 行之前调用此函数(基本上用对 cleanUp() 的调用替换每个评论 // free all allocated memory)。 我的问题是如何将指针 data1data2resultbuffer 传递给 cleanUp() 以便它们可以被释放?

这就是我想做的。这是正确的方法吗?

void cleanUp(void* p1, void* p2, void* p3, void* p4){
    // call to free for each pointer
}

或多或少,但也许更好的方法是使用 atexit() 注册回调。只要程序正常退出(通过 exit(),或在 return 从 main() 到启动代码),就会调用它。缺点是清理函数的签名是 void cleanup(void) — 没有参数。这意味着该函数必须能够访问全局(或文件范围)变量。但它为执行清理提供了最大的可靠性。

还有一种说法是,如果你的程序即将退出,你是否调用free()并不重要——系统无论如何都会释放内存。但是,如果您不释放内存,像 Valgrind 这样的程序会抱怨丢失内存 — 即使如此,释放它通常也是一种很好的做法。

如果您曾经被教导不要使用 goto,那么类似的东西就是使用 goto.

的好时机的典型例子
int main(int argc, char** argv){
    int ret_status = 0;
    // Some code
    // ...
    // ...
    void* data1 = malloc(someSize);
    void* data2 = malloc(someSize);
    double* result = malloc(resultSize);
    double* buffer = malloc(buffSize);
    // allocation failure: first exit point
    if(!(data1 && data2 && result && buffer)){
        ret_status = 1;
        goto cleanup;
    }
    // Some code
    // ...
    // Second exit point
    if(someOtherExitcondition){
        ret_status = 1;
        goto cleanup;
    }
    // Some code
    // ...

cleanup:
    // free all allocated memory     
    free(data1);
    free(data2);
    free(result);
    free(buffer);

    return ret_status;
}

如果与给定的示例不同,您还从嵌套函数调用 exit(),请查看 Jonathan Leffler 的回答中给出的使用 atexit()

只需调用

    cleanUp(data1, data2, result, buffer);

因为这些变量的值是已分配的指针,这就是您无论如何要传递给 free() 的内容。

我认为更好的方法是在函数末尾的清理部分使用 goto 语句。这是 goto 利大于弊的少数情况之一。想象一下,如果您有 50 个使用这种缓冲区的函数,并且它们都应该有自己相应的清理函数,您的代码会是什么样子。

查看 Christian Gibbons 的回答。

您应该考虑使用非标准 cleanup 功能,该功能由gccclang 支持,它简化了代码,并使您的代码更安全。

void free_void(void **value)     { free(*value); }
void free_double(double **value) { free(*value); }

int main()
{
    /* Return any time you like and allocated things get freed */

    __attribute__((cleanup(free_void))) void *data1 = malloc(someSize);
    __attribute__((cleanup(free_void))) void *data2 = malloc(someSize);
    __attribute__((cleanup(free_double))) double *result = malloc(resultSize);
    __attribute__((cleanup(free_double))) double *buffer = malloc(buffSize);

    return 0;
}

你可以用宏来简化这个:

static inline void free_void(void **value)     { free(*value); }
#define cleanup__void_ptr __attribute__((cleanup(free_void))) void *

static inline void free_double(double **value) { free(*value); }
#define cleanup__double_ptr __attribute__((cleanup(free_double))) double *

int main()
{
    /* Return any time you like and allocated things get freed */

    cleanup__void_ptr data1 = malloc(someSize);
    cleanup__void_ptr data2 = malloc(someSize);
    if(!data2) {
        return 0;
        /* calls `free()` on data1 and data2. */
    }
    cleanup__double_ptr result = malloc(resultSize);
    cleanup__double_ptr buffer = malloc(buffSize);

    return 0;
    /* calls `free()` on data1, data2, result and buffer */
}

我强烈建议不要使用 setjmp()/longjmp(),而是将清理函数附加到任何已分配的资源,这样您就可以在任何地方使用 return 语句。函数的任何调用者都应该检查 return 值。如果错误提示return立即处理,或者处理异常。这样你就不需要 setjmp()/longjmp().