我和我认为许多其他人在使用智能指针来包装 C++ 中的不安全内存操作(使用 RAII 等)方面取得了巨大成功。但是,当您有析构函数、类、运算符重载等时,包装内存管理更容易实现。
对于使用原始 C99 编写的人,您可以在哪里指出(不是双关语)来帮助安全内存管理?
谢谢。
我和我认为许多其他人在使用智能指针来包装 C++ 中的不安全内存操作(使用 RAII 等)方面取得了巨大成功。但是,当您有析构函数、类、运算符重载等时,包装内存管理更容易实现。
对于使用原始 C99 编写的人,您可以在哪里指出(不是双关语)来帮助安全内存管理?
谢谢。
这个问题有点老了,但我想我会花时间链接到我的 GNU 编译器(GCC、Clang、ICC、MinGW 等)的智能指针库。
这个实现依赖于清理变量属性,一个 GNU 扩展,在超出范围时自动释放内存,因此,它不是ISO C99,而是带有 GNU 扩展的 C99。
例子:
简单1.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)
在原始 C 中处理智能指针很困难,因为您没有语言语法来支持使用。我见过的大多数尝试都没有真正奏效,因为当对象离开作用域时,你没有析构函数运行的优势,而这正是智能指针起作用的真正原因。
如果您真的担心这一点,您可能想考虑直接使用垃圾收集器,并完全绕过智能指针要求。
您可能要考虑的另一种方法是Apache 使用的池化内存方法。如果您有与请求或其他短期对象相关联的动态内存使用情况,这将非常有效。您可以在请求结构中创建一个池,并确保始终从池中分配内存,然后在处理完请求后释放池。一旦你使用了一点,它听起来就不像它那么强大了。它几乎和 RAII 一样好。
您不能在 C 中使用智能指针,因为它没有提供必要的语法,但您可以通过练习避免泄漏。分配资源后立即编写资源释放代码。因此,无论何时编写 a ,都应立即在清理部分中malloc
编写相应的内容。free
在 CI 中经常看到“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_ptr
工作weak_ptr
原理。它在这里写得更详细:weak_ptr 如何工作?
您可以定义宏(例如 BEGIN 和 END)来代替大括号并触发自动销毁正在退出其范围的资源。这要求所有此类资源都由智能指针指向,该指针还包含指向对象析构函数的指针。在我的实现中,我在堆内存中保留了一个智能指针堆栈,在进入范围时记住堆栈指针,并在范围退出时调用记忆堆栈指针上方的所有资源的析构函数(END 或宏替换返回)。即使使用了 setjmp/longjmp 异常机制,这也能很好地工作,并且也清除了 catch 块和引发异常的范围之间的所有中间范围。有关实现,请参见https://github.com/psevon/exceptions-and-raii-in-c.git。
如果您在 Win32 中编码,您可能能够使用结构化异常处理来完成类似的事情。你可以这样做:
foo() {
myType pFoo = 0;
__try
{
pFoo = malloc(sizeof myType);
// do some stuff
}
__finally
{
free pFoo;
}
}
虽然不像 RAII 那样简单,但您可以将所有清理代码收集在一个地方并保证其被执行。
好的,这是您的选择。理想情况下,您将它们结合起来以获得更好的结果。在 C 的情况下,偏执狂是可以的。
编译时间:
运行:
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 !
*/