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

Ace*_*cer 3 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   
}
Run Code Online (Sandbox Code Playgroud)

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

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

我已经知道上面的情况,但是,为什么?有没有理由制造这样的限制?

- 更新 -

一个触发这个问题的完整故事:

我试图为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;
}
Run Code Online (Sandbox Code Playgroud)

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

Pra*_*ian 6

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

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

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


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

#define SHARED_PTR_GET_ADD_REF(sp_in, sp_name) ++(*sp_in.use_cnt); \
                                               shared_ptr sp_name = sp_in; 
Run Code Online (Sandbox Code Playgroud)

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

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

现场演示