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

时间:2009-04-28 21:06:18

标签: c memory pointers c99 smart-pointers

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

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

感谢。

9 个答案:

答案 0 :(得分:18)

这个问题有点陈旧,但我想我会花时间链接到我的smart pointer library GNU编译器(GCC,Clang,ICC,MinGW,...)。

此实现依赖于清理变量属性(GNU扩展),以便在超出范围时自动释放内存,因此, ISO C99,但C99具有GNU扩展。< / p>

示例:

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;
}

编译&amp; 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)

答案 1 :(得分:13)

在原始C中处理智能指针很困难,因为您没有语言语法来备份使用情况。我见过的大多数尝试并没有真正起作用,因为当对象离开范围时,你没有运行析构函数的优势,这正是使智能指针工作的原因。

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

答案 2 :(得分:8)

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

答案 3 :(得分:3)

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

此主题还有其他(一些更昂贵的)变种......

答案 4 :(得分:3)

您无法在C中执行智能指针,因为它不提供必要的语法,但您可以通过练习避免泄漏。分配资源后立即编写资源发布代码。因此,无论何时编写malloc,都应立即在清理部分编写相应的free

在C中我看到&#39; GOTO清理&#39;模式很多:

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 :(得分:2)

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

答案 6 :(得分:1)

如果您在Win32中进行编码,则可以使用structured exception handling来完成类似的操作。你可以这样做:

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

虽然不像RAII那么容易,但您可以在一个地方收集所有清理代码并保证它已被执行。

答案 7 :(得分:0)

好的,这是您的选择。理想情况下,将它们组合在一起可获得更好的结果。如果是C,妄想症可以。

编译时间:

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

运行时:

  1. 使用调试内存分配库,该库将malloc / free替换为其自己的实现,并跟踪内存使用情况。这样,您可以查看已分配但从未释放的块。我在Solaris上成功使用了它(将会记住它的名称)。
  2. 使用垃圾收集器。我在修复Hans-Boehm GC方面的经验非常丰富,同时修复了我没有源代码的非常泄漏的C应用程序。我可以看到内存消耗如何上升,然后在GC工作时下降。

答案 8 :(得分:-1)

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