memory management – Reference-counted smart pointer in C

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("nnOUT OF MEMORYnn");
    *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);
}
#include <stdio.h>
#include "shared_ptr.h"

static void example_destruct(volatile int * p){
    printf("int destruct %dn",*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("%dn",*(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("%dn",*(volatile int*)get_shared_ptr_ptr(&example_ptr));
    
    (*(volatile int*)get_shared_ptr_ptr(&example_ptr_3))=40;
}