C · Pointers · Systems programming
Long time since my latest Blog post :) I am currently playing around with C and used a few times a trick (at least a trick for me)
In C, a pointer normally holds a memory address. But there is a lesser-known trick: you can store a small integer value directly inside the pointer slot, without allocating any memory at all. This is useful when an API hands you a void * context slot and you just want to pass a small integer through it.
Consider this program. ap is a normal pointer — it holds the address of a and can be dereferenced. bp is different — it holds the value of b (2020) cast directly into a pointer slot.
#include <stdio.h>
int main()
{
int a = 10;
int *ap = &a;
int b = 2020;
void *bp = (void *)(unsigned long)b; // store value, not address
int c = (int)(unsigned long)bp; // cast back to read it
printf("a : %d at %p\n", a, &a);
printf("*ap : %d at %p pointing to %p\n", *ap, &ap, ap);
printf("b : %d at %p\n", b, &b);
printf("bp : %p at %p\n", bp, &bp); // NO dereference — use %p
printf("c : %d\n", c);
return 0;
}
Output:
a : 10 at 0x16efe2958
*ap : 10 at 0x16efe2950 pointing to 0x16efe2958
b : 2020 at 0x16efe294c
*bp : 0x7e4 at 0x16efe2940
c : 2020
Never dereference
bpwith*bp. The value 2020 is stored as-if it were an address (0x000007E4). Nothing lives at that address — dereferencing it causes an immediate segfault. This did happen to me several times and I guess because of muscle memory.
To print the stored value, skip the dereference and cast back instead:
printf("%d\n", (int)(unsigned long)bp); // 2020
printf("%p\n", bp); // 0x00000000000007e4
The double cast through unsigned long (or uintptr_t from <stdint.h>) is required. Casting an integer directly to void * is technically undefined behaviour in C.
int *ap = &a;
// ap ──► [ 10 ] dereference follows the arrow
void *bp = (void *)(unsigned long)b;
// bp = 0x000007E4 the value IS the pointer — no arrow to follow
With a normal pointer you follow the arrow to get the value. With a value stored in a pointer slot there is no arrow to follow — the value is the pointer itself, so you cast it directly.
pthread_create takes a void * argument for the thread function. When you only need to pass a small integer (like a thread index), you can avoid a heap allocation entirely by stuffing the value directly into that slot.
#include <stdio.h>
#include <pthread.h>
void *worker(void *arg)
{
int index = (int)(unsigned long)arg; // cast back — no dereference
printf("thread %d running\n", index);
return NULL;
}
int main()
{
pthread_t threads[4];
for (int i = 0; i < 4; i++)
{
// store i directly — no malloc needed
pthread_create(&threads[i], NULL, worker,
(void *)(unsigned long)i);
}
for (int i = 0; i < 4; i++)
{
pthread_join(threads[i], NULL);
}
return 0;
}
Compare this to the allocation-based approach, which requires a malloc per thread and a matching free inside the worker:
// with malloc — more code, easy to leak
int *idx = malloc(sizeof(int));
*idx = i;
pthread_create(&threads[i], NULL, worker, idx);
// worker must remember to free(arg)
The value must fit in a pointer — fine for thread indices, file descriptors, enum values, and small integers. For larger data structures, use the heap as normal.
The same pattern appears anywhere an API gives you a void * slot:
void * valuesThe rule is always the same — store the value with a double cast in, read it back with a double cast out, and never dereference.