There are two main ways to keep the size of a pointer.
One as just another variable along side:
size_t length = get_length();
int* stuff = (int*) malloc(length * sizeof(int));
do_stuff(stuff, length);
Another is a struct:
typedef struct ptr_with_length {
void* ptr;
size_t length;
} ptr_with_length;
size_t length = get_length();
ptr_with_length stuff = { malloc(length * sizeof(int)), length };
do_stuff(length);
But another way is storing the size of the pointer at the beginning of the pointer:
size_t length = get_length();
size_t* stuff = malloc(length * sizeof(int) + sizeof(size_t));
stuff[0] = length;
do_stuff((int*) (stuff + 1))
I wrote some functions which are basically wrappers around the stdlib functions to do this:
sptr.h
#ifndef SPTR_H
#define SPTR_H
#include <stdlib.h>
void* make_sptr(void* ptr, size_t size);
void* make_ptr(void* sptr);
void free_sptr(void* sptr);
void* malloc_sptr(size_t size);
void* realloc_sptr(void* sptr, size_t size);
void* calloc_sptr(size_t nitems, size_t size);
size_t sptr_size(void* sptr);
void* rel_realloc_sptr(void* sptr, long long int relative_size);
#endif // SPTR_H
sptr.c
#include <string.h>
#include "sptr.h"
/**
* Make a sptr from a regular pointer. Invalidates the pointer.
*
* @param ptr A pointer to a block of memory with `size` bytes allocated
* @param size The size (in bytes).
* @return NULL on error (With original pointer untouched), otherwise a new sptr.
*/
void* make_sptr(void* ptr, size_t size) {
size_t* sptr = (size_t*) realloc(ptr, size + sizeof(size_t));
if (sptr == NULL) {
return NULL;
}
memmove(sptr + 1, sptr, size);
sptr[0] = size;
return sptr + 1;
}
/**
* Make a regular pointer from a sptr that can be used by `free()`, `realloc()` and friends. Invalidates `sptr`.
*/
void* make_ptr(void* sptr) {
size_t size = sptr_size(sptr);
memmove(sptr, ((size_t*) sptr) - 1, size);
void* ptr = realloc(((size_t*) sptr) - 1, size);
if (ptr == NULL) {
return ((size_t*) sptr) - 1;
}
return ptr;
}
/**
* Frees the memory allocated to a sptr. Like `free(ptr)`, but for sptrs.
*/
void free_sptr(void* sptr) {
free(((size_t*) sptr) - 1);
}
/**
* Allocates a block of memory, and returns the pointer to the beginning of the memory.
* Like `malloc(size)`, but for sptrs.
*
* @param size The number of bytes to allocated.
* @return NULL if no memory is available, otherwise a pointer with `size` bytes of memory allocated.
*/
void* malloc_sptr(size_t size) {
size_t* sptr = (size_t*) malloc(size + sizeof(size_t));
if (sptr == NULL) {
return NULL;
}
sptr[0] = size;
return sptr + 1;
}
/**
* Resize the amount of memory allocated to a pointer. Like `realloc(ptr, size)`, but for sptrs.
* Invalidates the original sptr on success.
*
* @param sptr The sptr to resize
* @param size The new number of bytes to allocate
* @return NULL if the reallocation was unsuccessful (With the input sptr untouched), a new sptr otherwise.
*/
void* realloc_sptr(void* sptr, size_t size) {
size_t* new_sptr = (size_t*) realloc(((size_t*) sptr) - 1, size + sizeof(size_t));
if (new_sptr == NULL) {
return NULL;
}
new_sptr[0] = size;
return new_sptr + 1;
}
/**
* Like `malloc_sptr(nitems * size)`, but sets all the allocated bytes to 0. Like `calloc(nitems, size)`, but for sptrs.
*
* @param nitems The number of items to set.
* @param size The size of each item (e.g. `sizeof(int)`).
* @return NULL if no memory is available, the pointer to the begining of a block of memory otherwise.
*/
void* calloc_sptr(size_t nitems, size_t size) {
size_t* sptr = (size_t*) calloc(nitems * size + sizeof(size_t), 1);
if (sptr == NULL) {
return NULL;
}
sptr[0] = nitems * size;
return sptr + 1;
}
/**
* Returns the size of the `sptr`.
*/
size_t sptr_size(void* sptr) {
return *(((size_t*) sptr) - 1);
}
/**
* Reallocates memory, adding or subtracting allocated bytes relative to the current size.
*
* If the new size would be negative, zero bytes are allocated. Invalidates the original sptr on success.
*
* @param sptr The sptr to resize
* @param relative_size The number of bytes to add (Or subtract if negative)
* @return NULL if the reallocation was unsuccessful (With the input sptr untouched), a new sptr otherwise.
*/
void* rel_realloc_sptr(void* sptr, long long int relative_size) {
long long int new_ssize = (long long int) sptr_size(sptr) + relative_size;
size_t new_size = new_ssize < 0 ? 0U : (size_t) new_ssize;
size_t* new_sptr = (size_t*) realloc(((size_t*) sptr) - 1, new_size + sizeof(size_t));
if (new_sptr == NULL) {
return NULL;
}
new_sptr[0] = new_size;
return new_sptr + 1;
}
The functions defined are:
make_sptr
which takes a regular pointer, reallocs enough for asize_t
, moves over all of the data, writes the size, and returns the pointer offset past the size.make_ptr
which moves the data in the sptr to where thesize_t
was and truncates it to the size. Currently, it returns the same sptr offset to the beginning ifrealloc
to truncate it fails (Which it apparantly can in places whererealloc
is justmalloc + memmove + free
). Should this just returnNULL
? Currently, the only difference between a failure ofrealloc
and a success is accessing past the intended length will be garbage instead of a potential seg fault. I think this behaviour is acceptable, as those addresses shouldn't be accessed anyways. Since it is a corner case (Badrealloc
implementation for shrinking and out of memory), the slightly extra memory usage of 1size_t
shouldn't be too much of a problem.free_sptr
, a wrapper aroundfree
.malloc_sptr
, a wrapper aroundmalloc
.realloc_sptr
, a wrapper aroundrealloc
.calloc_sptr
, a wrapper aroundcalloc
.sptr_size
which retrieves the size of the allocated memory.rel_realloc_sptr
which is likerealloc
, but relative to the current size. What type should I use for the relative size? I am currently using along long int
for the difference between twosize_t
s. Maybeptrdiff_t
? However, since I am not accounting for any sort of overflow (as not that much memory should be allocated in the first place), maybe this is unneccesary, and I should use aint
instead.
You should be able to do normal pointer stuff, like sizeof
, arithmetic and memcopy
, strlen
, etc..
I am looking for comments on style, answers to the questions above and whether this could be used as a library (Else what would need to be changed before it could be).
As for correctness, the following is an excerpt from The C99 standard (WG14/N1124. Committee Draft -- May 6, 2005. ISO/IEC 9899:TC2) about free() (Page 325):
if the argument does not match a pointer earlier returned by the calloc, malloc, or realloc function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.
Since adding a constant sizeof(size_t)
and then subtracting it will "match" the one returned by realloc, this is fine:
void* test = ++malloc(100); // Ignoring NULL for this
free(test - 1);
// "Matches" (is equal to) the original pointer
// returned by malloc; works as expected.
Some example usage of this:
#include <stdio.h>
#include "sptr.h"
void print_int_sptr(int* sptr); // To appease GCCs -Werror=missing-declaration
void print_int_sptr(int* sptr) { // No need to pass the length.
size_t length = sptr_size(sptr) / sizeof(int);
if (length) {
printf("%d", sptr[0]);
for (size_t i = 1; i < length; i++) {
printf(", %d", sptr[i]);
}
}
printf("\n");
}
int main(void) {
int* sptr = malloc_sptr(2 * sizeof(int));
if (sptr == NULL) {
return 1;
}
sptr[0] = 123;
sptr[1] = 231;
print_int_sptr(sptr); // Expect: 123, 231
// Allocate 1 more int
int* temp = rel_realloc_sptr(sptr, sizeof(int));
if (temp == NULL) {
free_sptr(sptr);
return 1;
}
sptr = temp;
sptr[2] = 321;
print_int_sptr(sptr); // Expect: 123, 231, 321
free_sptr(sptr);
sptr = calloc_sptr(5, sizeof(int));
if (sptr == NULL) {
return 1;
}
sptr[4] = 5;
print_int_sptr(sptr); // Expect: 0, 0, 0, 0, 5
free_sptr(sptr);
}