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;
}

