【问题标题】:Smart pointers/safe memory management for C?C的智能指针/安全内存管理?
【发布时间】:2010-10-22 10:31:55
【问题描述】:

我和我认为许多其他人在使用智能指针来包装 C++ 中的不安全内存操作(使用 RAII 等)方面取得了巨大成功。但是,当您有析构函数、类、运算符重载等时,包装内存管理更容易实现。

对于使用原始 C99 编写代码的人,您可以在哪里指出(不是双关语)来帮助安全内存管理?

谢谢。

【问题讨论】:

    标签: c memory pointers c99 smart-pointers


    【解决方案1】:

    这个问题有点老了,但我想我会花时间链接到我的smart pointer library 用于 GNU 编译器(GCC、Clang、ICC、MinGW 等)。

    这个实现依赖于清理变量属性,一个 GNU 扩展,在超出范围时自动释放内存,因此,不是 ISO C99,而是带有 GNU 扩展的 C99。

    例子:

    simple1.c:

    #include <stdio.h>
    #include <csptr/smart_ptr.h>
    
    int main(void) {
        smart int *some_int = unique_ptr(int, 1);
    
        printf("%p = %d\n", some_int, *some_int);
    
        // some_int is destroyed here
        return 0;
    }
    

    编译和 Valgrind 会议:

    $ gcc -std=gnu99 -o simple1 simple1.c -lcsptr
    $ valgrind ./simple1
    ==3407== Memcheck, a memory error detector
    ==3407== Copyright (C) 2002-2013, and GNU GPL\'d, by Julian Seward et al.
    ==3407== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
    ==3407== Command: ./simple1 
    ==3407==
    0x53db068 = 1
    ==3407==
    ==3407== HEAP SUMMARY:
    ==3407==     in use at exit: 0 bytes in 0 blocks
    ==3407==   total heap usage: 1 allocs, 1 frees, 48 bytes allocated
    ==3407==
    ==3407== All heap blocks were freed -- no leaks are possible
    ==3407==
    ==3407== For counts of detected and suppressed errors, rerun with: -v
    ==3407== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    【讨论】:

      【解决方案2】:

      在原始 C 中处理智能指针很困难,因为您没有语言语法来支持使用。我见过的大多数尝试都没有真正起作用,因为你没有在对象离开作用域时运行析构函数的优势,而这正是智能指针起作用的原因。

      如果您真的担心这一点,您可能想考虑直接使用garbage collector,并完全绕过智能指针要求。

      【讨论】:

      • @Calmarius 他们的工作方式多种多样。见:en.wikipedia.org/wiki/Garbage_collection_(computer_science)
      • 我明白了。我问了你链接的GC。它声称它可以在未修改的 C 程序上工作,只替换 malloc 和 realloc。但是它如何定位指向分配块的指针呢?它们可以在程序中到处复制。
      • 它似乎扫描分配的块并尝试将所有整数值解释为指针。那么它的效率对于大块来说可能很糟糕。
      • 垃圾收集器链接已损坏。
      【解决方案3】:

      您可能要考虑的另一种方法是Apache uses 的池化内存方法。如果您有与请求或其他短期对象相关联的动态内存使用情况,这将非常有效。您可以在请求结构中创建一个池,并确保始终从池中分配内存,然后在处理完请求后释放池。一旦你用了一点,它听起来就不像它那么强大了。它几乎和 RAII 一样好。

      【讨论】:

        【解决方案4】:

        您不能在 C 中使用智能指针,因为它没有提供必要的语法,但您可以通过练习避免泄漏。分配资源后立即编写资源释放代码。所以每当你写一个malloc,你应该立即在清理部分写相应的free

        在 C 中,我经常看到“GOTO cleanup”模式:

        int foo()
        {
            int *resource = malloc(1000);
            int retVal = 0;
            //...
            if (time_to_exit())
            {
                retVal = 123;
                goto cleanup;
            }
        cleanup:
            free(resource);
            return retVal;
        }
        

        在 C 语言中,我们也使用很多上下文来分配东西,同样的规则也可以应用:

        int initializeStuff(Stuff *stuff)
        {
            stuff->resource = malloc(sizeof(Resource));
            if (!stuff->resource) 
            {
                return -1; ///< Fail.
            }
            return 0; ///< Success.
        }
        
        void cleanupStuff(Stuff *stuff)
        {
            free(stuff->resource);
        }
        

        这类似于对象的构造函数和析构函数。只要您不将分配的资源分配给其他对象,它就不会泄漏,指针也不会悬空。

        编写一个自定义分配器来跟踪分配并写入泄漏块atexit 并不难。

        如果您需要提供指向已分配资源的指针,您可以为其创建包装上下文,并且每个对象都拥有一个包装上下文而不是资源。这些包装器共享资源和一个计数器对象,该对象跟踪使用情况并在没有人使用时释放对象。这就是 C++11 的 shared_ptrweak_ptr 的工作原理。这里写得更详细:How does weak_ptr work?

        【讨论】:

          【解决方案5】:

          splintGimpel PC-Lint 这样的静态代码分析工具在这里可能会有所帮助——您甚至可以通过将它们连接到您的自动“持续集成”风格的构建服务器中来使它们具有适度的“预防性”。 (你确实有其中之一,对吧?:grin:)

          这个主题还有其他(一些更昂贵的)变体......

          【讨论】:

            【解决方案6】:

            您可以定义宏(例如 BEGIN 和 END)来代替大括号并触发自动销毁正在退出其范围的资源。这要求所有此类资源都由智能指针指向,该指针还包含指向对象析构函数的指针。在我的实现中,我在堆内存中保留了一个智能指针堆栈,在进入范围时记住堆栈指针,并在范围退出时调用记忆堆栈指针上方的所有资源的析构函数(END 或宏替换返回)。即使使用了 setjmp/longjmp 异常机制,这也能很好地工作,并且也清除了 catch 块和引发异常的范围之间的所有中间范围。实现见https://github.com/psevon/exceptions-and-raii-in-c.git

            【讨论】:

              【解决方案7】:

              如果您在 Win32 中编码,您也许可以使用 structured exception handling 来完成类似的事情。你可以这样做:

              foo() {
                  myType pFoo = 0;
                  __try
                  {
                      pFoo = malloc(sizeof myType);
                      // do some stuff
                  }
                  __finally
                  {
                      free pFoo;
                  }
              }
              

              虽然不如 RAII 简单,但您可以将所有清理代码收集在一个地方并保证其被执行。

              【讨论】:

                【解决方案8】:

                好的,这是您的选择。理想情况下,您将它们结合起来以获得更好的结果。在 C 的情况下,偏执是可以的。

                编译时间:

                1. 在 GCC 中使用清理变量属性。之后你必须坚持使用 GCC。这限制了代码的可移植性,因为您只能针对存在 GCC 的平台。
                2. 在 Windows 上使用 SEH(结构化异常处理)。这进一步限制了您的可移植性,因为您必须使用 Microsoft 编译器。如果您的目标是专门的 Windows,那么它将适合您。
                3. 使用静态代码分析工具揭示潜在的内存泄漏。不能完美地工作,但可以帮助发现微不足道的泄漏。不会影响您的便携性。

                运行时:

                1. 使用调试内存分配库,它用自己的实现替换 malloc/free 并跟踪内存使用情况。这样您就可以看到已分配但从未释放的块。我在 Solaris 上取得了成功(将尝试记住它的名称)。
                2. 使用垃圾收集器。在修复我没有源代码的漏洞非常多的 C 应用程序时,我对 Hans-Boehm GC 有过积极的体验。我可以看到内存消耗是如何攀升的,然后在 GC 完成工作时急剧下降。

                【讨论】:

                  【解决方案9】:
                  Sometimes i use this approach and it seems good :)
                  
                  Object *construct(type arg, ...){
                  
                      Object *__local = malloc(sizeof(Object));
                      if(!__local)
                          return NULL;
                      __local->prop_a = arg;
                      /* blah blah */
                  
                  
                  } // constructor
                  
                  void destruct(Object *__this){
                  
                     if(__this->prop_a)free(this->prop_a);
                     if(__this->prop_b)free(this->prop_b);
                  
                  } // destructor
                  
                  Object *o = __construct(200);
                  if(o != NULL)
                     ;;
                  
                  // use
                  
                  destruct(o);
                  
                  /*
                    done !
                  */
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2014-06-02
                    • 2014-05-10
                    • 1970-01-01
                    • 2016-10-09
                    • 2015-05-19
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多