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

发布于 2024-07-18 08:20:55 字数 162 浏览 9 评论 0原文

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

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

谢谢。

I, and I think many others, have had great success using smart pointers to wrap up unsafe memory operations in C++, using things like RAII, et cetera. However, wrapping memory management is easier to implement when you have destructors, classes, operator overloading, et cetera.

For someone writing in raw C99, where could you point (no pun intended) to help with safe memory management?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

迷雾森÷林ヴ 2024-07-25 08:20:55

这个问题有点老了,但我想我会花时间链接到我的 智能指针库 适用于 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)

The question is a bit old, but I figured I would take the time to link to my smart pointer library for GNU compilers (GCC, Clang, ICC, MinGW, ...).

This implementation relies on the cleanup variable attribute, a GNU extension, to automatically free the memory when going out of scope, and as such, is not ISO C99, but C99 with GNU extensions.

Example:

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

Compilation & Valgrind session:

$ 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)
ま昔日黯然 2024-07-25 08:20:55

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

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

It's difficult to handle smart pointers in raw C, since you don't have the language syntax to back up the usage. Most of the attempts I've seen don't really work, since you don't have the advantages of destructors running when objects leave scope, which is really what makes smart pointers work.

If you're really worried about this, you might want to consider just directly using a garbage collector, and bypassing the smart pointer requirement altogether.

维持三分热 2024-07-25 08:20:55

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

Another approach that you might want to consider is the pooled memory approach that Apache uses. This works exceptionally well if you have dynamic memory usage that is associated with a request or other short-lived object. You can create a pool in your request structure and make sure that you always allocate memory from the pool and then free the pool when you are done processing the request. It doesn't sound nearly as powerful as it is once you have used it a little. It is almost as nice as RAII.

慢慢从新开始 2024-07-25 08:20:55

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

在 CI 中,我们经常看到“GOTO 清理”模式:

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 的工作原理。 这里写得更详细:weak_ptr 是如何工作的?

You cannot do smart pointers in C because it does not provide necessary syntax, but you can avoid leaks with practice. Write the resource release code immediately after you allocated it. So whenever you write a malloc, you should write the corresponding free immediately in a cleanup section.

In C I see the 'GOTO cleanup' pattern a lot:

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

In C we also use a lot of contexts which allocate stuff, the same rule can be applied for that too:

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

This is analogous to the object constructors and destructors. As long as you don't give away the allocated resources to other objects it won't leak and pointers won't dangle.

It's not difficult to write a custom allocator that tracks allocations and writes leaking blocks atexit.

If you need to give away pointers to the allocated resources you can create wrapper contexts for it and each object owns a wrapper context instead of the resource. These wrappers share the resource and a counter object, which tracks the usage and frees the objects when no one uses it. This is how C++11's shared_ptr and weak_ptr works. It's written in more detail here: How does weak_ptr work?

゛清羽墨安 2024-07-25 08:20:55

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

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

Static code analysis tools like splint or Gimpel PC-Lint may help here -- you can even make these moderately "preventative" by wiring them into your automatic "continuous-integration" style build server. (You do have one of those, right? :grin:)

There are other (some more expensive) variants on this theme too...

筱武穆 2024-07-25 08:20:55

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

编译时:

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

运行时:

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

Ok, so here are your options. Ideally, you combine them to get better result. In case of C, paranoia is fine.

Compile-time:

  1. Use the cleanup variable attribute in GCC. You have to stick to GCC after that. This limits portability of your code because you can only target platforms for which GCC exists.
  2. Use SEH (structured exception handling) on Windows. This limits your portability even further because your have to use the Microsoft compiler. If your target is exclusively Windows, then it will work for you.
  3. Use the static code analysis tool revealing potential memory leaks. Doesn't work perfectly, but can help find trivial leaks. Doesn't affect your portability.

Runtime:

  1. Use the debug memory allocation library which replaces malloc/free with its own implementations and tracks memory usage. This way you can see blocks that were allocated but never released. I had success with the one for Solaris (will try to remember its name).
  2. Use a garbage collector. I had a positive experience with Hans-Boehm GC while fixing a very leaky C application that I didn't have the source code for. I could see how memory consumption was climbing, then it plummeted when the GC did its work.
故事和酒 2024-07-25 08:20:55

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

You can define macros, for example BEGIN and END, to be used in place of braces and trigger automatic destruction of resources that are exiting their scope. This requires that all such resources are pointed to by smart pointers that also contain pointer to the destructor of the object. In my implementation I keep a stack of smart pointers in heap memory, memorize the stack pointer at entry to a scope, and call destructors of all resources above the memorized stack pointer at scope exit (END or macro replacement for return). This works nicely even if setjmp/longjmp exception mechanism is used, and cleans up all the intermediate scopes between the catch-block and the scope where the exception was thrown, too. See https://github.com/psevon/exceptions-and-raii-in-c.git for the implementation.

仅此而已 2024-07-25 08:20:55

如果您使用 Win32 进行编码,则可以使用 结构化异常处理来完成类似的事情。 您可以这样做:

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

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

If you are coding in Win32 you might be able to use structured exception handling to accomplish something similar. You could do something like this:

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

While not quite as easy as RAII, you can collect all of your cleanup code in one place and guarantee that it is executed.

格子衫的從容 2024-07-25 08:20:55
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 !
*/
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 !
*/
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文