【问题标题】:Cleanup function in CC中的清理功能
【发布时间】:2020-02-26 19:33:23
【问题描述】:

我有一个 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 行之前调用此函数(基本上将每个评论// free all allocated memory 替换为对cleanUp() 的调用)。 我的问题是如何将指针 data1,data2,resultbuffer 传递给 cleanUp() 以便它们可以被释放?

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

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

【问题讨论】:

  • 我假设 cleanUp 只会在你退出之前被调用一次?
  • 是的,我想在程序退出之前调用一次以清理所有分配的内存,但程序中可能有多个退出点
  • 哦,我明白了。那么为什么你的 cleanUp 方法没有相同的声明呢?
  • public void cleanUp(void* data1, void* data2, double* result, double* buffer)
  • 将所有分配的缓冲区放在一个结构中;然后,您可以创建一个 cleanup() 方法,该方法将指向结构实例的指针作为其参数,并释放该方法中的所有缓冲区。您可以从结构实例在范围内的任何位置调用该方法。

标签: c malloc free


【解决方案1】:

如果您曾经被教导永远不要使用 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()

【讨论】:

  • 这是我推荐的方法。
  • 记得处理分配错误。 if (data1 == NULL || data2 == NULL || result == NULL || buffer == NULL) goto cleanup;
  • 这不是goto 的主要示例,并且它没有实现问题的要求,即在每个exit 调用中执行清理,因为exit 之外的exit 将绕过这些. C 标准为此提供了一个工具,atexit 调用,它注册了一个在程序退出时要调用的函数。
  • @KamilCuk 我正在决定是否要通过处理分配错误来增加示例的复杂性。我想既然所有的分配都是在这里完成的,我们可以做类似的事情并保持简单。我将征用该示例的假设条件之一来处理错误分配。
  • @EricPostpischil 这取决于您对问题的解释。对于OP给出的示例,这将是最简单的方法。如果我们期望程序退出不同的函数,那就是我开始研究使用atexit 的时候,这需要额外的工作来确保函数可以访问需要释放的标识符。在我看来,这两种方法都可以作为这个问题的参考,以便未来的用户可以看到在退出函数和退出程序的情况下工作的清理方法。
【解决方案2】:

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

还有一种说法是,如果你的程序即将退出,无论你是否调用free() 都无关紧要——系统无论如何都会释放内存。然而,如果你不释放它,像Valgrind 这样的程序会抱怨内存丢失——即使这样释放它通常也是一个好习惯。

【讨论】:

  • 如果我将指针声明data1data2resultbuffer 移到main 函数之外会怎样?
  • 这就是你需要做的一个单独的清理功能才能访问它们。您可以将它们设为static,这样它们就不会污染全局符号空间(不应有其他文件访问它们),但您的清理功能可以使用它们。它们甚至默认初始化为 NULL(零),因此即使尚未分配,也可以安全地将它们传递给 free()。请注意,即使出口位于调用层次结构深处的函数中(在从main() 调用的函数中,或者那些调用的函数之一,令人作呕),也会调用清理函数。
  • 创建一个单独的静态对象来使用 exit-handler 函数可能更简洁——一个结构或链表或要释放内存的东西(和/或要执行的其他任务) 被列出。然后只有“一个东西”,其目的比散布静态指针更容易明确,即使这个东西只是一个具有指针成员的结构。
  • 是的,@EricPostpischil — 我在代码中使用了这样的系统,我需要发布在 setjmp() 和匹配的 longjmp() 之间分配的数据。
  • 由于将标识符提供给atexit() 所涉及的额外工作,我认为这里有一个如何设置它的示例。
【解决方案3】:

只需调用

    cleanUp(data1, data2, result, buffer);

因为这些变量具有已分配的指针作为值,这就是您将传递给free() 的值。

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

查看 Christian Gibbons 的答案。

【讨论】:

    【解决方案4】:

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

    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 语句。函数的任何调用者都应该检查返回值。如果指示错误,则立即返回,或处理异常。这样就不需要setjmp()/longjmp()

    【讨论】:

      猜你喜欢
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2020-04-03
      • 1970-01-01
      • 2020-02-16
      • 2021-02-07
      • 1970-01-01
      • 2020-03-05
      相关资源
      最近更新 更多