为什么GCC / Clang的清理属性不能与函数参数一起使用

时间:2014-01-27 14:41:25

标签: c gcc raii

尽管cleanup属性是仅由GCC / Clang支持的扩展,但我认为 是纯C中最接近RAII的近似值。例如

#define loc_str __attribute__((cleanup(free_loc_str)))

void free_loc_str(char **str)
{ if(str && *str) free(*str); }

int main(void)
{
    loc_str char *s = malloc(10);
    return 0;  // Great! s is freed when it exit its scope   
}

但是,该属性仅适用于自动范围但不适用于函数参数。即。

void func(loc_str char *str)
{
    return; // XXX - str will not be freed (compiled without any warning) 
}

我已经了解上述情况,但是,为什么?有没有理由制造这种限制?

- 更新 -

触发此问题的完整故事:

我尝试为C创建共享指针(或智能指针)。以下是非线程安全和简化的代码段

struct impl_t;
struct impl_t* ctor();
void dtor(struct impl_t* inst);

struct shared_ptr_s
{
    struct impl_t* inst;
    int *use_cnt;
};

void free_shared(struct shared_ptr_s* ptr)
{
    if(!ptr) return;
    if(0 == --(*ptr->use_cnt)) {
        dtor(ptr->inst);
        free(ptr->use_cnt);
    }
    ptr->inst = 0;
    ptr->use_cnt = 0;
}

#define shared_ptr struct shared_ptr_s __attribute__((cleanup(free_shared)))

void func(shared_ptr sp)
{
    // shared_ptr loc_sp = sp;  // works but make no sense
    return; // sp will not be freed since cleanup function is not triggered
}

int main(void)
{
    shared_ptr sp = { 
        .inst = ctor(), 
        .use_cnt = malloc(sizeof(int)) 
    };
    ++*sp.use_cnt; // please bear this simplification.

    {    
        ++*sp.use_cnt;
        shared_ptr sp2 = sp;
    } // sp.inst is still there since use_cnt > 0 

    ++*sp.use_cnt;
    func(sp); // leak!

    return 0;
}

这就是为什么我希望清理属性可以使用函数参数 - 尽可能地手动释放。

1 个答案:

答案 0 :(得分:6)

大概是因为,在函数参数的情况下,思维是调用者的,而不是被调用者,负责管理参数的内存。如果你确实需要被调用者在退出时释放参数,那么解决方法很简单,只需制作一个由cleanup属性装饰的参数的本地副本。

void func(char *str)
{
    loc_str char *str1 = str;
    return; 
} // now str1 will be free when func exits

当然,在这种情况下,不要在调用者传递给func()的参数上使用cleanup属性,否则你手上就会有双倍空格。


对于您的用例,我建议创建一个增加使用次数的宏,并声明类型为shared_ptr的局部变量。

#define SHARED_PTR_GET_ADD_REF(sp_in, sp_name) ++(*sp_in.use_cnt); \
                                               shared_ptr sp_name = sp_in; 

在任何需要增加使用次数的地方使用该宏。因此,使用调试语句的示例如下所示:

#include <stdlib.h>
#include <stdio.h>

struct shared_ptr_s
{
//    struct impl_t* inst;
    int *use_cnt;
};

typedef struct shared_ptr_s shared_ptr_t;           // unadorned type
#define shared_ptr struct shared_ptr_s __attribute__((cleanup(free_shared)))

#define SHARED_PTR_GET_ADD_REF(sp_in, sp_name) ++(*sp_in.use_cnt); \
                                               printf("add use_cnt = %d\n", *sp_in.use_cnt); \
                                               shared_ptr sp_name = sp_in; 

void free_shared(struct shared_ptr_s* ptr)
{
    if(!ptr) return;
    printf("del use_cnt = %d\n", *ptr->use_cnt - 1); 
    if(0 == --(*ptr->use_cnt)) {
//        dtor(ptr->inst);
        printf("freeing %p\n", (void *)ptr->use_cnt);
        free(ptr->use_cnt);
    }
//    ptr->inst = 0;
    ptr->use_cnt = 0;
}

void func(shared_ptr_t sp)
{
    SHARED_PTR_GET_ADD_REF(sp, sp_loc);
    return;
}

int main(void)
{
    shared_ptr_t sp = {         // original type does not use __attribute__(cleanup)
//        .inst = ctor(), 
        .use_cnt = malloc(sizeof(int)) 
    };
    SHARED_PTR_GET_ADD_REF(sp, sp_loc);

    func(sp_loc);

    return 0;
}

Live demo