在C

时间:2017-06-29 07:58:45

标签: c variables macros

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

很抱歉这个长短语,但是我正在使用xnu内核,你有许多内置引用计数器的元素,并且在使用它时必须不要忘记取消这个元素以避免内存泄漏。

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

struct proc;
typedef struct proc * proc_t;

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

{
    proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid);
    //the rest of the code in this scope 
}

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

{ 
    proc_t myproc = proc_find(mypid)
    //the rest of the code in scope
    proc_rele(myproc);
}

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

3 个答案:

答案 0 :(得分: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;
}

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

gcc main.c -o main

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

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)

答案 1 :(得分: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)

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

#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块接受以下语句,但是如果我们实际上没有将该语句放在宏定义中,我们可以使用普通代码块跟随宏调用,它将“神奇地“属于我们新的范围控制结构语法,只需遵循扩展的for

任何值得使用的优化器都会在最低优化设置下删除循环标志。请注意,与标志冲突的名称并不是一个大问题(即,您实际上并不需要gensym),因为该标志的作用域是循环体,并且任何嵌套循环都会在它们使用时安全地隐藏它相同的国旗名称。

这里的奖励是清除变量的范围受到限制(它不能在声明后立即在化合物之外使用)和视觉上明确(因为所述化合物)。

优点:

  • 这是标准C,没有扩展名
  • 控制流程很简单
  • 它实际上(某种程度上)比__attribute__ __cleanup__
  • 更简洁

缺点:

  • 它不提供“完整”RAII(即不会防止goto或C ++例外:__cleanup__通常在C ++机制下实现,因此它更完整)。更严重的是,它无法防范早期return(感谢@Voo)。 (你可以至少防止错位的break - 如果你愿意 - 通过在switch (0) default:的末尾添加第三行M_AROUND_BLOCK2。 / LI>
  • 不是每个人都同意语法扩展宏(但考虑到你 在这里扩展C的语义,所以...)

答案 2 :(得分:4)

我知道这不是你想听到的,但我恳请你不要这样做。

单一的返回点是完全可以接受的C风格,在此之前一切都得到清理。由于没有例外,这很容易做到,并且通过查看函数很容易验证。

使用宏hackery或编译器&#34;功能&#34;要做到这一点是不被接受的C风格。在您阅读和理解之后,这将成为每个人的负担。最后它实际上并没有给你带来太大的收获。