在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风格。 在你阅读和理解之后,对每个人来说都是一个负担。 而最终它并没有给你带来太多的收益。