资源获取是C语言中的初始化

时间:2017-02-03 13:30:21

标签: c gcc raii

问题是:您能否仅使用我在此问题底部提供的资源帮助我更好地理解C语言(而不是c ++)中的RAII宏?我试图在脑海中分析它,以便理解它所说的内容以及它是如何理解的(在我看来它没有意义)。语法很难。 问题的焦点是:我无法阅读和理解奇怪的语法及其在C语言中的实现。 例如,我可以轻松阅读,理解和分析(对我来说)以下交换宏:

#define myswap(type,A,B) {type _z; _z = (A); (A) = (B); (B) = _z;} 

(以下段落取自本书:理解C指针)

  

在C语言中,GNU编译器提供非标准扩展   支持RAII。

     

GNU扩展使用名为RAII_VARIABLE的宏。它声明了一个   变量并与变量关联:

  • A type
  • 创建变量时执行的函数
  • 变量超出范围时执行的函数

    宏如下所示:

    #define RAII_VARIABLE(vartype,varname,initval,dtor) \
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)
    
         

    示例:

    void raiiExample() {
    RAII_VARIABLE(char*, name, (char*)malloc(32), free);
    strcpy(name,"RAII Example");
    printf("%s\n",name);
    } 
    
    int main(void){
        raiiExample();
    }
    

执行此功能时,将显示字符串“RAII_Example”。在不使用GNU扩展的情况下可以实现类似的结果。

3 个答案:

答案 0 :(得分:4)

好的,让我们逐行查看宏观部分

#define RAII_VARIABLE(vartype,varname,initval,dtor) \

第一行当然是宏名称加上其参数列表。这里没什么意外,我们似乎传递一个类型,一个令牌名称,一些表达式来初始化一个变量,以及一些希望最终被调用的析构函数。到目前为止,这很容易。

void _dtor_ ## varname (vartype * v) { dtor(*v); } \

第二行声明了一个函数。它采用提供的令牌varname并在其前面添加前缀_dtor_##运算符指示预处理器将两个令牌融合到一个令牌中)。此函数将指向vartype的指针作为参数,并使用该参数调用提供的析构函数。

这种语法在这里可能会出乎意料(比如使用##运算符,或者它依赖于声明嵌套函数的能力),但它还没有真正的魔力。魔术出现在第三行:

vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)

这里声明了变量,没有__attribute__()这看起来非常简单:vartype varname = (initvar)。神奇的是__attribute__((cleanup(_dtor_ ## varname)))指令。它指示编译器确保在变量超出范围时调用提供的函数。

__attribute__()语法是编译器提供的语言扩展,因此您可以深入了解实现定义的行为。您不能依赖提供相同__attribute__((cleanup()))的其他编译器。许多人可能会提供它,但没有必要。一些较旧的编译器甚至可能根本不知道__attribute__()语法,在这种情况下,标准过程是#define __attribute__()为空,从代码中剥离所有__attribute__()声明。您不希望RAII变量发生这种情况。因此,如果您依赖__attribute__(),请知道您已经失去了使用任何标准符合编译器进行编译的能力。

答案 1 :(得分:3)

当然,您可以在不使用RAII的情况下实现任何目标。 RAII用例它不必考虑明确释放资源。像:

这样的模式
leader

需要你注意释放内存,如果没有,你会有内存泄漏。由于正确发布资源并不总是很容易,RAII为您提供了一种自由化的方式:

void f() {
    char *v = malloc(...);
    // use v
    free v;
}

有趣的是,无论执行的路径是什么,都将发布ressource。因此,如果您的代码是一种意大利面条代码,充满了复杂的条件和测试等,RAII可以让您放心释放...

答案 2 :(得分:3)

语法有点棘手,因为__attribute__ ((cleanup))期望传递一个将指针指向变量的函数。来自GCC documentation(强调我的):

  

该函数必须带一个参数,一个指向兼容类型的指针   使用变量。函数的返回值(如果有的话)是   忽略。

请考虑关注不正确的示例:

char *name __attribute__((cleanup(free))) = malloc(32);

像这样实现它会简单得多,但是在这种情况下free函数隐含地将指针带到name,其类型为char ** 。你需要一些方法来强制传递正确的对象,这就是RAII_VARIABLE类似函数的宏的想法。

RAII_VARIABLE的简化和非通用化身将是定义函数,比如raii_free

#include <stdlib.h>

void raii_free(char **var) { free(*var); }

int main(void)
{
    char *name __attribute__((cleanup(raii_free))) = malloc(32);
    return 0;
}