Skip to content

Commit 51787a6

Browse files
committed
Add a generic reference counting
1 parent e1a67bb commit 51787a6

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ purpose of these programs is to be illustrative and educational.
1212
- [preempt\_sched](preempt_sched/): A preemptive userspace multitasking based on a SIGALRM signal.
1313
* Multi-threading Paradigms
1414
- [tpool](tpool/): A lightweight thread pool.
15+
- [refcnt](refcnt/): A generic reference counting.
1516
* [Producer–consumer problem](https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem)
1617
- [spmc](spmc/): A concurrent single-producer/multiple-consumer queue.
1718
- [mpsc](mpsc/): An unbounded lockless single-consumer/multiple-producer FIFO queue.

refcnt/Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
all:
2+
gcc -o main -Wall -O2 -fsanitize=thread main.c -lpthread
3+
4+
clean:
5+
rm -f main

refcnt/main.c

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Generic reference counting in C.
3+
*
4+
* Use refcnt_malloc/refcnt_unref in instead of malloc/free. Use refcnt_ref to
5+
* duplicate a reference as necessary. Use refcnt unref to stop using a pointer.
6+
*
7+
* The resulting string must be released using refcnt_unref since refcnt_strdup
8+
* function utilizes refcnt_malloc.
9+
*
10+
* This implementation is thread-safe with the exception of refcnt_realloc. If
11+
* you need to use refcnt_realloc in a multi-threaded environment, you must
12+
* synchronize access to the reference.
13+
*
14+
* If you define REFCNT_CHECK, references passed into refcnt_ref and and
15+
* refcnt_unref will be checked that they were created by refcnt_malloc. This is
16+
* useful for debugging, but it will slow down your program somewhat.
17+
*
18+
* If you define REFCNT_TRACE, refcnt_malloc and refcnt_unref will print
19+
* the line number and file name where they were called. This is useful for
20+
* debugging memory leaks or use after free errors.
21+
*/
22+
23+
#include <assert.h>
24+
#include <stdatomic.h>
25+
#include <stddef.h>
26+
#include <stdlib.h>
27+
#include <string.h>
28+
29+
#define REFCNT_CHECK
30+
31+
#define maybe_unused __attribute__((unused))
32+
33+
typedef struct {
34+
#ifdef REFCNT_CHECK
35+
int magic;
36+
#endif
37+
atomic_uint refcount;
38+
char data[];
39+
} refcnt_t;
40+
41+
#ifdef REFCNT_TRACE
42+
#define _REFCNT_TRACE(call) \
43+
({ \
44+
fprintf(stderr, "%s:%d:(%s) %s", __FILE__, __LINE__, __FUNCTION__, \
45+
#call); \
46+
call; \
47+
})
48+
#define refcnt_malloc refcnt_t_malloc
49+
#define refcnt_realloc refcnt_t_realloc
50+
#define refcnt_ref refcnt_t_ref
51+
#define refcnt_unref refcnt_t_unref
52+
#define refcnt_strdup refcnt_t_strdup
53+
#endif
54+
55+
#define REFCNT_MAGIC 0xDEADBEEF
56+
57+
static maybe_unused void *refcnt_malloc(size_t len)
58+
{
59+
refcnt_t *ref = malloc(sizeof(refcnt_t) + len);
60+
if (!ref)
61+
return NULL;
62+
#ifdef REFCNT_CHECK
63+
ref->magic = REFCNT_MAGIC;
64+
#endif
65+
atomic_init(&ref->refcount, 1);
66+
return ref->data;
67+
}
68+
69+
static maybe_unused void *refcnt_realloc(void *ptr, size_t len)
70+
{
71+
refcnt_t *ref = (void *) (ptr - offsetof(refcnt_t, data));
72+
#ifdef REFCNT_CHECK
73+
assert(ref->magic == REFCNT_MAGIC);
74+
#endif
75+
ref = realloc(ref, sizeof(refcnt_t) + len);
76+
if (!ref)
77+
return NULL;
78+
return ref->data;
79+
}
80+
81+
static maybe_unused void *refcnt_ref(void *ptr)
82+
{
83+
refcnt_t *ref = (void *) (ptr - offsetof(refcnt_t, data));
84+
#ifdef REFCNT_CHECK
85+
assert(ref->magic == REFCNT_MAGIC && "Invalid refcnt pointer");
86+
#endif
87+
atomic_fetch_add(&ref->refcount, 1);
88+
return ref->data;
89+
}
90+
91+
static maybe_unused void refcnt_unref(void *ptr)
92+
{
93+
refcnt_t *ref = (void *) (ptr - offsetof(refcnt_t, data));
94+
#ifdef REFCNT_CHECK
95+
assert(ref->magic == REFCNT_MAGIC && "Invalid refcnt pointer");
96+
#endif
97+
if (atomic_fetch_sub(&ref->refcount, 1) == 1)
98+
free(ref);
99+
}
100+
101+
static maybe_unused char *refcnt_strdup(char *str)
102+
{
103+
refcnt_t *ref = malloc(sizeof(refcnt_t) + strlen(str) + 1);
104+
if (!ref)
105+
return NULL;
106+
#ifdef REFCNT_CHECK
107+
ref->magic = REFCNT_MAGIC;
108+
#endif
109+
atomic_init(&ref->refcount, 1);
110+
strcpy(ref->data, str);
111+
return ref->data;
112+
}
113+
114+
#ifdef REFCNT_TRACE
115+
#undef refcnt_malloc
116+
#undef refcnt_realloc
117+
#undef refcnt_ref
118+
#undef refcnt_unref
119+
#undef refcnt_strdup
120+
#define refcnt_malloc(len) _REFCNT_TRACE(refcnt_t_malloc(len))
121+
#define refcnt_realloc(ptr, len) _REFCNT_TRACE(refcnt_t_realloc(ptr, len))
122+
#define refcnt_ref(ptr) _REFCNT_TRACE(refcnt_t_ref(ptr))
123+
#define refcnt_unref(ptr) _REFCNT_TRACE(refcnt_t_unref(ptr))
124+
#define refcnt_strdup(ptr) _REFCNT_TRACE(refcnt_t_strdup(ptr))
125+
#endif
126+
127+
#include <pthread.h>
128+
#include <stdio.h>
129+
#include <stdlib.h>
130+
131+
#define N_ITERATIONS 100
132+
133+
static void *test_thread(void *arg)
134+
{
135+
char *str = arg;
136+
for (int i = 0; i < N_ITERATIONS; i++) {
137+
char *str2 = refcnt_ref(str);
138+
fprintf(stderr, "Thread %u, %i: %s\n", (unsigned int) pthread_self(), i,
139+
str2);
140+
refcnt_unref(str2);
141+
}
142+
refcnt_unref(str);
143+
return NULL;
144+
}
145+
146+
#define N_THREADS 64
147+
148+
int main(int argc, char **argv)
149+
{
150+
/* Create threads */
151+
pthread_t threads[N_THREADS];
152+
153+
/* Create a new string that is count referenced */
154+
char *str = refcnt_strdup("Hello, world!");
155+
156+
/* Start the threads, passing a new counted copy of the referece */
157+
for (int i = 0; i < N_THREADS; i++)
158+
pthread_create(&threads[i], NULL, test_thread, refcnt_ref(str));
159+
160+
/* We no longer own the reference */
161+
refcnt_unref(str);
162+
163+
/* Whichever thread finishes last will free the string */
164+
for (int i = 0; i < N_THREADS; i++)
165+
pthread_join(threads[i], NULL);
166+
167+
void *ptr = malloc(100);
168+
/* This should cause a heap overflow while checking the magic num which the
169+
* sanitizer checks.
170+
* Leaving commented out for now
171+
*/
172+
// refcnt_ref(ptr);
173+
174+
free(ptr);
175+
return 0;
176+
}

0 commit comments

Comments
 (0)