C语言的智能指针/安全内存管理?

58

我认为许多人都通过使用智能指针成功地封装了C ++中不安全的内存操作,例如RAII等。但是,在没有析构函数,类,运算符重载等情况下实现内存管理更加困难。

对于在裸露的C99中编写代码的人,您可以从哪里获得有关安全内存管理的帮助?

谢谢。


你能指出来吗。哎呀,我希望这是个双关语。 - Balázs Börcsök
9个回答

33

这个问题有点老了,但我觉得我可以花时间为大家提供我的智能指针库,适用于GNU编译器(GCC、Clang、ICC、MinGW等)。

该实现依赖于cleanup变量属性,这是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)

14
在原始的 C 语言中,处理智能指针很困难,因为你没有语言语法来支持使用。我看到的大多数尝试都不起作用,因为当对象离开范围时,你没有析构函数运行的优势,这正是智能指针起作用的原因。
如果你真的担心这个问题,你可能需要考虑直接使用 垃圾回收器,从而完全绕过智能指针的要求。

@Calmarius 它们有各种不同的工作方式。请参见:http://en.wikipedia.org/wiki/Garbage_collection_(computer_science) - Reed Copsey
我明白了。我问的是你提供的GC。它声称可以在未经修改的C程序上工作,只需替换malloc和realloc。但是它如何定位指向分配块的指针?它们可能在程序中被复制。 - Calmarius
看起来它会扫描分配的块,并尝试将所有整数值解释为指针。因此,对于大块来说,它的效率可能非常糟糕。 - Calmarius
2
垃圾回收器链接已损坏。 - Robert Harvey

8

你可能想要考虑的另一种方法是池内存的方法,这也是Apache使用的方法。如果你需要使用与请求或其他短暂对象相关联的动态内存使用,那么这种方法非常有效。你可以在请求结构中创建一个池,并确保总是从该池中分配内存,然后在处理完请求后释放该池。一旦你使用过它,就会发现它比表面上看起来的强大多了。它几乎和RAII一样好用。


6
你不能在 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语言中,我们也经常使用很多上下文(context)来分配内存,同样的规则也可以应用于这方面:
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);
}

这类似于对象构造函数和析构函数。只要不将分配的资源分配给其他对象,它就不会泄漏并且指针也不会悬空。
编写自定义分配器来跟踪分配并在退出时写入泄漏块,不难实现。
如果需要将分配的资源指针传递给其他对象,可以为其创建包装器上下文,并使每个对象拥有包装器上下文而不是资源。这些包装器共享资源和计数器对象,该对象跟踪使用情况并在没有用户使用时释放对象。这就是C++11的shared_ptr和weak_ptr的工作方式。更详细的说明可以参考这里:How does weak_ptr work?

3
静态代码分析工具,如splintGimpel PC-Lint,可能会有所帮助--您甚至可以将它们连接到自动“持续集成”样式的构建服务器中,从而使其适度地具有“预防性”。(您有这样的服务器吗?:笑脸:)
还有其他(一些更昂贵)类似的变体。

3
好的,这里是您的选项。理想情况下,您可以将它们组合以获得更好的结果。在C语言中,如果出现偏执症也是可以接受的。
编译时:
  1. 使用GCC中的清理变量属性。之后您必须坚持使用GCC。这限制了代码的可移植性,因为您只能针对存在GCC的平台。
  2. 在Windows上使用SEH(结构化异常处理)。这进一步限制了您的可移植性,因为您必须使用微软编译器。如果您的目标仅限于Windows,则可以使用此方法。
  3. 使用静态代码分析工具来发现潜在的内存泄漏。虽然不完美,但可以帮助找到一些简单的泄漏问题。不影响您的可移植性。
运行时:
  1. 使用调试内存分配库,它会用自己的实现替换malloc/free,并跟踪内存使用情况。这样你就可以看到已分配但从未释放的块。我在Solaris上使用过一个成功的调试库(尽量记住它的名字)。
  2. 使用垃圾回收器。我在修复一个非常泄漏的C应用程序时使用了Hans-Boehm GC,并取得了积极的经验。我无法获取源代码,但能看到内存消耗如何上升,然后在GC工作时急剧下降。

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

1

如果您正在编写 Win32 程序,您可能可以使用结构化异常处理来实现类似的功能。您可以尝试以下代码:

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

虽然不像RAII那样简单,但你可以将所有的清理代码收集到一个地方,并保证它被执行。


-3
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 !
*/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接