在C中自动释放堆栈变量

Ira*_*d K 32 c variables macros

不幸的是,在C中没有任何智能指针..但是有可能构建一个宏来包装变量声明并在离开声明变量的范围时使用该变量作为输入变量调用函数调用吗?

很抱歉这个长短语,但我正在使用xnu内核,你有许多内置引用计数器的元素,并且一定不要忘记在使用它时不用这个元素来避免内存泄漏.

例如,如果我有以下类型proc_t:

struct proc;
typedef struct proc * proc_t;
Run Code Online (Sandbox Code Playgroud)

我想在范围内基于此类型声明堆栈变量,例如:

{
    proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid);
    //the rest of the code in this scope 
}
Run Code Online (Sandbox Code Playgroud)

在预处理器分析宏之后和编译之前,我希望生成以下代码:

{ 
    proc_t myproc = proc_find(mypid)
    //the rest of the code in scope
    proc_rele(myproc);
}
Run Code Online (Sandbox Code Playgroud)

有没有办法在C中定义这样的宏?

小智 39

您可以在GCC中使用清理变量属性.请看一下: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;
}
Run Code Online (Sandbox Code Playgroud)

如果将源代码保存在名为main.c的文件中,则可以使用以下命令对其进行编译:

gcc main.c -o main
Run Code Online (Sandbox Code Playgroud)

并通过以下方式验证是否存在任何内存泄漏:

valgrind ./main
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

  • 我想这是一个可行的解决方案,如果您可以将自己绑定到专有的,特定于编译器的扩展.但是,人们不得不怀疑,为什么不将C代码编译为C++以受益于语言中内置的RAII?然后你不依赖任何非便携式的东西,结果甚至更好.(我知道,我知道,C++不是C的*严格*超集,但是它非常接近,在典型的C代码库中只需要很少甚至不需要修改.) (8认同)
  • clang也支持cleanup属性 (7认同)
  • @CodyGray并非每个有效的C代码在C++中都有任何语义,即使它是编译的.例如,大多数C代码使用联合进行类型惩罚,但这是C++中的UB.尽管GCC确实以与C相同的方式定义它,但在这种情况下,切换到C++并不会使代码更具可移植性. (5认同)
  • "专有"有多种定义; 特别是,这一个:"非标准,仅由一个特定组织使用,如*标准*的专有扩展".我没有创新这种用法; 虽然Mozilla就像GCC一样是开源的,但"专有"也就这种方式用于[CSS扩展](https://developer.mozilla.org/en-US/docs/Web/CSS/List_of_Proprietary_CSS_Features). .关键是这种行为不是*C语言标准的一部分,因此是不可移植的,具有将你锁定到GCC中的*实用*意义,而不是合法的意义.@猫 (4认同)

Leu*_*nko 18

C确实提供了一种在语法上将代码放在首先执行的其他代码之前的方法:for块.请记住,for结构的第3节可以包含任意表达式,并且总是在执行主块之后运行.

因此,您可以通过在宏中包装块来创建一个宏,该宏在给定的以下代码块之后进行预定调用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)
Run Code Online (Sandbox Code Playgroud)

......你可以像这样使用它:

#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
  }
}
Run Code Online (Sandbox Code Playgroud)

这里的技巧是for块接受以下语句,但是如果我们实际上没有将该语句放在宏定义中,我们可以使用普通代码块跟随宏调用,它将"神奇地"属于我们的新范围 -控制结构语法,只需通过以下扩展for.

任何值得使用的优化器都会在最低优化设置下删除循环标志.请注意,与标志冲突的名称不是一个大问题(即你真的不需要gensym这个),因为标志的范围是循环体,如果它们使用相同的标志名称,任何嵌套循环都将安全地隐藏它.

这里的好处是清理变量的范围是有限的(它不能在声明之后立即在化合物之外使用)和视觉上明确(因为所述化合物).

优点:

  • 这是标准C,没有扩展名
  • 控制流程很简单
  • 它实际上(某种程度上)不那么冗长 __attribute__ __cleanup__

缺点:

  • 它不提供"完整"RAII(即不会防止goto或C++异常:__cleanup__通常使用C++机制实现,因此它更完整).更严重的是,它不能提前防范return(感谢@Voo).(你至少可以防止错位break- 如果你想 - 通过添加第三行,switch (0) default:到最后M_AROUND_BLOCK2.)
  • 不是每个人都同意语法扩展宏(但考虑到你这里扩展C的语义,所以...)

  • 除了不防止诸如goto或C++异常之类的模糊事物之外,它也不能防止看起来更容易引起麻烦的简单"返回". (15认同)
  • 哇.和大多数这样的事情一样,我不知道是惊讶还是恐惧,或两者兼而有之.预处理器运行后,您的示例看起来如何? (10认同)
  • @Voo是我喜欢拆分内存管理和功能组件的地方.被调用的函数设置内存,然后调用函数组件,当_that_返回(无论出于何种原因)时,自动完成清理. (3认同)