在C中自动释放堆栈variables
不幸的是,在C没有任何智能指针..但是有可能build立一个macros,包装variables声明和调用函数调用该variables作为inputvariables时,离开variables声明的范围?
对不起,这个词很长,但是我正在研究xnu内核,其中有许多内置引用计数器的元素,当使用它来避免内存泄漏时,不要忘记取消这个元素。
例如,如果我有以下types的proc_t
:
struct proc; typedef struct proc * proc_t;
我想在一个范围内声明一个基于这个types的堆栈variables,例如:
{ proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid); //the rest of the code in this scope }
在预处理器分析macros并编译之前,我期望生成的以下代码是:
{ proc_t myproc = proc_find(mypid) //the rest of the code in scope proc_rele(myproc); }
有没有什么办法来定义像C这样的macros?
你可以在GCC中使用清理variables属性。 请看看这个: http : //echorand.me/site/notes/articles/c_cleanup/cleanup_attribute_c.html
示例代码:
#include <stdio.h> #include <stdlib.h> void free_memory(void **ptr) { printf("Free memory: %p\n", *ptr); free(*ptr); } int main(void) { // Define variable and allocate 1 byte, the memory will be free at // the end of the scope by the free_memory function. The free_memory // function will get the pointer to the variable *ptr (double pointer // **ptr). void *ptr __attribute__ ((__cleanup__(free_memory))) = malloc(1); return 0; }
如果将源代码保存在名为main.c的文件中,则可以使用以下命令进行编译:
gcc main.c -o main
并validation是否有任何内存泄漏:
valgrind ./main
valgrind的输出示例:
==1026== Memcheck, a memory error detector ==1026== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==1026== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==1026== Command: ./main ==1026== Free memory: 0x51ff040 ==1026== ==1026== HEAP SUMMARY: ==1026== in use at exit: 0 bytes in 0 blocks ==1026== total heap usage: 1 allocs, 1 frees, 1 bytes allocated ==1026== ==1026== All heap blocks were freed -- no leaks are possible ==1026== ==1026== For counts of detected and suppressed errors, rerun with: -v ==1026== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
C确实提供了一种在代码之前首先执行代码的方法: for
代码块。 请记住, for
结构的第3节可以包含任意expression式,并且总是在主块执行后运行。
因此,您可以创build一个macros,在给定的以下代码块之后,通过在macros中包含for
块来进行预定义的调用:
#define M_GEN_DONE_FLAG() _done_ ## __LINE__ #define M_AROUND_BLOCK2(FLAG, DECL, BEFORE, AFTER) \ for (int FLAG = (BEFORE, 0); !FLAG; ) \ for (DECL; !FLAG; FLAG = (AFTER, 1)) #define M_AROUND_BLOCK(DECL, BEFORE, AFTER) M_AROUND_BLOCK2(M_GEN_DONE_FLAG(), DECL, BEFORE, AFTER) #define M_CLEANUP_VAR(DECL, CLEANUP_CALL) M_AROUND_BLOCK(DECL, (void)0, CLEANUP_CALL)
…你可以像这样使用它:
#include <stdio.h> struct proc; typedef struct proc * proc_t; proc_t proc_find(int); void proc_rele(proc_t); void fun(int mypid) { M_CLEANUP_VAR (proc_t myproc = proc_find(mypid), proc_rele(myproc)) { printf("%p\n", &myproc); // just to prove it's in scope } }
这里的诀窍是for
块接受下面的语句,但是如果我们实际上没有把这个语句放到macros定义中,我们可以用一个普通的代码块来跟踪macros的调用,它会“神奇地”属于我们的新的scoped控制结构的语法,只是凭借以下扩展后for
。
任何值得使用的优化器都会在最低优化设置下删除循环标志。 请注意,与标志冲突的名称并不是一个巨大的问题(即,您并不真正需要一个gensym
),因为该标志的作用域是循环体,并且任何嵌套的循环将安全地隐藏它们,如果它们使用相同的标志名称。
这里的好处是,清理variables的范围受到限制(在声明后不能立即在化合物之外使用)和视觉显式(由于所述化合物)。
优点:
- 这是标准的C,没有扩展名
- 控制stream程很简单
- 它实际上(不知何故)比
__attribute__ __cleanup__
缺点:
- 它不提供“完整的”RAII(即不会防止
goto
或C ++exception:__cleanup__
通常是在C ++机制下实现的,因此它更完整)。 更严重的是,它不能防止早日return
(谢谢@Voo)。 (你至less可以防止错位的break
– 如果你想要的话 – 通过添加第三行,switch (0) default:
到M_AROUND_BLOCK2
的结尾。 - 不是每个人都同意扩展语法的macros(但是考虑到你在这里扩展C的语义,所以…)
我知道这不是你想听到的,但我敦促你不要这样做。
有一个单一的回报点,这是完全可以接受的C风格,在这之前,一切都被清理干净。 由于没有例外,所以这很容易实现,并且通过查看function容易validation。
使用macroshackery或编译器“function”来做到这一点是不被接受的C风格。 在你阅读和理解之后,对每个人来说都是一个负担。 而最终它并没有给你带来太多的收益。