Despite the cleanup attribute is an extension that supported by GCC/Clang only, I think it s the nearest approximation to RAII in pure C. e.g.
#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
}
Though, the attribute works only with auto scope but not function parameter. i.e.
void func(loc_str char *str)
{
return; // XXX - str will not be freed (compiled without any warning)
}
I already know above situation, but, why? Is there any reason to create such restriction?
-- Update --
A full story that trigger this question:
I tried to create a shared pointer(or smart pointer) for C. Following is a non-thread safe and simplified snippet
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;
}
That's why I wish the cleanup attribute can work with function parameter - eliminate manually free as much as possible.
Presumably because, in case of function parameters, the thinking is that it's the caller's, and not the callee's, responsibility to manage memory for the arguments. If you do need the callee to free the argument upon exit, the workaround is simple enough, just make a local copy of the argument that is adorned by the cleanup attribute.
void func(char *str)
{
loc_str char *str1 = str;
return;
} // now str1 will be free when func exits
Of course, in this case, do not use the cleanup attribute on the argument passed to func()
by the caller, or you'll have a double free on your hands.
For your use case, I'd suggest creating a macro that increments the use count and declares a local variable of type shared_ptr
.
#define SHARED_PTR_GET_ADD_REF(sp_in, sp_name) ++(*sp_in.use_cnt); \
shared_ptr sp_name = sp_in;
Use that macro everywhere you need to increment use count. So your example, with debug statements, would look like this:
#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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With