About
I've been experimenting with gcc's __cleanup__ attribute, and thought it'd be a great fit for a memory-safe smart pointer for C.
This is the implementation. It requires you to use either the helper macros SHARED_PTR_VAR_* or declare a variable as shared_ptr_t __attribute__((__cleanup__(cleanup_shared_ptr))) var={0};, and since it relies on gcc's extensions, it obviously requires a compiler that supports them.
The reference counting is thread-safe, but the pointer access itself is not synchronized.
shared_ptr.h
#pragma once
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include "util.h"
typedef struct shared_ptr_cntrl {
void * data;
void (*cleanup)(void*);
pthread_mutex_t mutex;
int64_t count;
int64_t cntrl_count;
} shared_ptr_cntrl_t;
typedef struct shared_ptr {
shared_ptr_cntrl_t * cntrl;
} shared_ptr_t;
shared_ptr_t create_shared_ptr(void ** data,void (*cleanup)(void*));
void cleanup_shared_ptr(shared_ptr_t *);
shared_ptr_t copy_shared_ptr(shared_ptr_t *);
static inline shared_ptr_t move_shared_ptr(shared_ptr_t * p){
shared_ptr_t tmp={p->cntrl};
p->cntrl=NULL;
return tmp;
}
static inline void * get_shared_ptr_ptr(shared_ptr_t * p){
return (p->cntrl)?p->cntrl->data:NULL;
}
#define SHARED_PTR_FROM(type,cleanup,expr) ({\
type * v=calloc(1,sizeof(type));\
if(!v) err_exit("\n\nOUT OF MEMORY\n\n");\
*v=expr;\
create_shared_ptr((void**)&v,(void(*)(void*))cleanup);\
})
#define SHARED_PTR_FROM_E(cleanup,expr) SHARED_PTR_FROM(__typeof__((expr)),cleanup,expr)
#define SHARED_PTR_VAR_FROM_E(name,cleanup,expr) CLEANUP_VAR_E(cleanup_shared_ptr,name,SHARED_PTR_FROM_E(cleanup,expr))
#define SHARED_PTR_VAR_E(name,expr) CLEANUP_VAR_E(cleanup_shared_ptr,name,expr)
#define SHARED_PTR_VAR_FROM(type,name,cleanup,expr) CLEANUP_VAR_E(cleanup_shared_ptr,name,SHARED_PTR_FROM(type,cleanup,expr));
util.h
#define CLEANUP(cleanup) __attribute__((__cleanup__(cleanup)))
#define CLEANUP_VAR(type,cleanup,name) type __attribute__((__cleanup__(cleanup))) name
#define CLEANUP_VAR_E(cleanup,name,expr) CLEANUP_VAR(__typeof__(expr),cleanup,name) = (expr) ;
__attribute__((format(printf,1,2)))
__attribute__((__noreturn__)) void err_exit(const char * fmt,...);
shared_ptr.c
#include "shared_ptr.h"
shared_ptr_t create_shared_ptr(void ** data,void (*cleanup)(void*)){
if(!*data){
return (shared_ptr_t){NULL};//avoid allocation for NULL pointers
}
shared_ptr_cntrl_t * cntrl=calloc(1,sizeof(shared_ptr_cntrl_t));
cntrl->data=*data;
*data=NULL;
cntrl->cleanup=cleanup;
cntrl->count=1;
cntrl->cntrl_count=1;
if(pthread_mutex_init(&cntrl->mutex,NULL)){
err_exit("Failed to create mutex for shared_ptr");
}
return (shared_ptr_t){cntrl};
}
void cleanup_shared_ptr(shared_ptr_t * p){
if(p->cntrl){
if(pthread_mutex_lock(&p->cntrl->mutex)){
err_exit("Failed to lock mutex for shared_ptr");
}
p->cntrl->count--;
p->cntrl->cntrl_count--;
if(p->cntrl->count==0){
if(p->cntrl->cleanup){
p->cntrl->cleanup(p->cntrl->data);
}
free(p->cntrl->data);
p->cntrl->data=NULL;
if(p->cntrl->cntrl_count==0){
pthread_mutex_t m=p->cntrl->mutex;
free(p->cntrl);
p->cntrl=NULL;
if(pthread_mutex_unlock(&m)){
err_exit("Failed to unlock mutex for shared_ptr");
}
if(pthread_mutex_destroy(&m)){
err_exit("Failed to destroy mutex for shared_ptr");
}
return;
}
}
if(pthread_mutex_unlock(&p->cntrl->mutex)){
err_exit("Failed to unlock mutex for shared_ptr");
}
}
}
shared_ptr_t copy_shared_ptr(shared_ptr_t * p){
if(p->cntrl){
if(pthread_mutex_lock(&p->cntrl->mutex)){
err_exit("Failed to lock mutex for shared_ptr");
}
p->cntrl->count++;
p->cntrl->cntrl_count++;
if(pthread_mutex_unlock(&p->cntrl->mutex)){
err_exit("Failed to unlock mutex for shared_ptr");
}
}
return (shared_ptr_t){p->cntrl};
}
util.c
#include "util.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
void err_exit(const char * fmt,...){
va_list arg;
va_start(arg,fmt);
vfprintf(stderr,fmt,arg);
va_end(arg);
exit(1);
}
Example Code
#include <stdio.h>
#include "shared_ptr.h"
static void example_destruct(volatile int * p){
printf("int destruct %d\n",*p);
}
int main(){
SHARED_PTR_VAR_FROM(volatile int,example_ptr,example_destruct,20);
SHARED_PTR_VAR_E(example_ptr_2,copy_shared_ptr(&example_ptr));
printf("%d\n",*(volatile int*)get_shared_ptr_ptr(&example_ptr_2));
SHARED_PTR_VAR_E(example_ptr_3,move_shared_ptr(&example_ptr_2));
(*(volatile int*)get_shared_ptr_ptr(&example_ptr_3))=23;
{
SHARED_PTR_VAR_E(example_ptr_4,copy_shared_ptr(&example_ptr));
SHARED_PTR_VAR_E(example_ptr_5,copy_shared_ptr(&example_ptr));
SHARED_PTR_VAR_E(example_ptr_6,copy_shared_ptr(&example_ptr));
SHARED_PTR_VAR_E(example_ptr_7,copy_shared_ptr(&example_ptr));
SHARED_PTR_VAR_E(example_ptr_8,copy_shared_ptr(&example_ptr));
}
printf("%d\n",*(volatile int*)get_shared_ptr_ptr(&example_ptr));
(*(volatile int*)get_shared_ptr_ptr(&example_ptr_3))=40;
}