I've been working on a C project where I needed a small, thread-safe shared pointer, and so I wrote this implementation.
It's primary use in the project is as a way of allowing multiple threads to have ownership of a resource at once. This means it's very important that it is thread safe (and also doesn't leak, obviously)
SharedPtr.h#ifndef SHARED_PTR_H
#define SHARED_PTR_H
typedef struct shared_ptr* SharedPtr;
typedef void(*RawPtrDestructor_t)(void*);
SharedPtr SharedPtr_create(void* rawPtr, RawPtrDestructor_t destructorFunc);
SharedPtr SharedPtr_copy(restrict SharedPtr rhs);
void* SharedPtr_get(restrict SharedPtr sharedPtr);
#ifndef SHARED_PTR_NO_THREAD_SAFETY
void SharedPtr_lockMutex(restrict SharedPtr sharedPtr);
void SharedPtr_unlockMutex(restrict SharedPtr sharedPtr);
#endif //SHARED_PTR_NO_THREAD_SAFETY
void SharedPtr_free(SharedPtr sharedPtr);
#endif //SHARED_PTR_H
SharedPtr.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef SHARED_PTR_NO_THREAD_SAFETY
#include <pthread.h>
#endif //SHARED_PTR_NO_THREAD_SAFETY
#include "SharedPtr.h"
struct shared_ptr
{
struct shared_ptr* previous;
struct shared_ptr* next;
#ifndef SHARED_PTR_NO_THREAD_SAFETY
pthread_mutex_t* sharedMutex;
#endif //SHARED_PTR_NO_THREAD_SAFETY
void* rawPtr;
RawPtrDestructor_t destructor;
};
static void genericDestructor(void* ptr)
{
free(ptr);
ptr = NULL;
}
static SharedPtr mallocSharedPtr(void)
{
//Create the shared pointer
SharedPtr sharedPtr = malloc(sizeof(struct shared_ptr));
//Check to make sure the malloc didn't fail
if (!sharedPtr)
{
fprintf(stderr, "Alloc error at %s:%i. Aborting...\n", __FILE__, __LINE__);
abort();
}
return sharedPtr;
}
SharedPtr SharedPtr_create(void* rawPtr, RawPtrDestructor_t destructorFunc)
{
//Create the shared pointer
SharedPtr sharedPtr = mallocSharedPtr();
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Create the mutex
int result = pthread_mutex_init(sharedPtr->sharedMutex, NULL);
//Check that it didn't fail
if (result != 0)
{
fprintf(stderr, "Pthread mutex init error '%s' at %s:%i. Aborting...\n", strerror(result), __FILE__, __LINE__);
abort();
}
#endif //SHARED_PTR_NO_THREAD_SAFETY
//Set the raw pointer
sharedPtr->rawPtr = rawPtr;
//If the destructor isn't specified, use the generic destructor
if (!destructorFunc) {sharedPtr->destructor = &genericDestructor;}
//Otherwise just use the one that's given
else {sharedPtr->destructor = destructorFunc;}
//Make sure previous and next are NULL
sharedPtr->previous = NULL;
sharedPtr->next = NULL;
//Return the shared pointer
return sharedPtr;
}
SharedPtr SharedPtr_copy(restrict SharedPtr rhs)
{
//Create the shared pointer
SharedPtr newSharedPtr = mallocSharedPtr();
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Set the mutex
newSharedPtr->sharedMutex = rhs->sharedMutex;
//Lock the mutex
pthread_mutex_lock(rhs->sharedMutex);
#endif //SHARED_PTR_NO_THREAD_SAFETY
//Set the raw pointer and destructor
newSharedPtr->rawPtr = rhs->rawPtr;
newSharedPtr->destructor = rhs->destructor;
//Set the 'previous' pointer to this shared pointer
newSharedPtr->previous = rhs;
//Set the 'next' pointer to NULL
newSharedPtr->next = NULL;
//Set the 'next' pointer of this shared pointer to the new shared pointer
rhs->next = newSharedPtr;
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Unlock the mutex
pthread_mutex_unlock(rhs->sharedMutex);
#endif //SHARED_PTR_NO_THREAD_SAFETY
//Return the new shared pointer
return newSharedPtr;
}
void* SharedPtr_get(restrict SharedPtr sharedPtr)
{
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Create a variable to store the pointer
void* ret = NULL;
//Lock the mutex
pthread_mutex_lock(sharedPtr->sharedMutex); //Is the mutex locking/unlocking here even necessary?
//Set the pointer
ret = sharedPtr->rawPtr;
//Unlock the mutex
pthread_mutex_unlock(sharedPtr->sharedMutex);
//Return the pointer
return ret;
#else
//Return the raw pointer
return sharedPtr->rawPtr;
#endif //SHARED_PTR_NO_THREAD_SAFETY
}
#ifndef SHARED_PTR_NO_THREAD_SAFETY
void SharedPtr_lockMutex(restrict SharedPtr sharedPtr)
{
//Lock the shared mutex
pthread_mutex_lock(sharedPtr->sharedMutex);
}
void SharedPtr_unlockMutex(restrict SharedPtr sharedPtr)
{
//Unlock the shared mutex
pthread_mutex_unlock(sharedPtr->sharedMutex);
}
#endif //SHARED_PTR_NO_THREAD_SAFETY
void SharedPtr_free(restrict SharedPtr sharedPtr)
{
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Lock the mutex
pthread_mutex_lock(sharedPtr->sharedMutex);
#endif //SHARED_PTR_NO_THREAD_SAFETY
//If this is the last shared pointer
if (!sharedPtr->previous && !sharedPtr->next)
{
//Call the destructor on the raw pointer
sharedPtr->destructor(sharedPtr->rawPtr);
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Unlock the mutex
pthread_mutex_unlock(sharedPtr->sharedMutex);
//Destroy the mutex
int result = pthread_mutex_destroy(sharedPtr->sharedMutex);
if (result != 0)
{
fprintf(stderr, "Pthread mutex destroy error '%s' at %s:%i. Aborting...\n", strerror(result), __FILE__, __LINE__);
abort();
}
//Free the mutex
free(sharedPtr->sharedMutex);
#endif //SHARED_PTR_NO_THREAD_SAFETY
}
//If there are more shared pointers
else
{
//Remove the pointer from the linked list
if (sharedPtr->next) {sharedPtr->next->previous = sharedPtr->previous;}
if (sharedPtr->previous) {sharedPtr->previous->next = sharedPtr->next;}
#ifndef SHARED_PTR_NO_THREAD_SAFETY
//Unlock the mutex
pthread_mutex_unlock(sharedPtr->sharedMutex);
#endif //SHARED_PTR_NO_THREAD_SAFETY
}
//Free the shared pointer's memory
free(sharedPtr);
sharedPtr = NULL;
}
I decided to use void pointers rather than a macro system (where calling a macro defines a version of the shared pointer with a specific pointer type) as I feel it's simpler and easier to write.
And a little example to showcase what it does:
#include "SharedPtr.h"
#include <stdio.h>
#include <stdlib.h>
void destructor(void* ptr)
{
(void)ptr;
puts("Destructor called!");
}
int main(void)
{
void* myRawPtr = NULL;
SharedPtr ptr1 = SharedPtr_create(myRawPtr, &destructor);
SharedPtr ptr2 = SharedPtr_copy(ptr1);
SharedPtr_free(ptr1);
SharedPtr_free(ptr2);
}
As expected, the program only outputs "Destructor called!" once.
I've written code in C++ for a few years but I've only just started writing in C, so any input would be helpful!