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 a size_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 the size_t was and truncates it to the size. Currently, it returns the same sptr offset to the beginning if realloc to truncate it fails (Which it apparantly can in places where realloc is just malloc + memmove + free). Should this just return NULL? Currently, the only difference between a failure of realloc 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 (Bad realloc implementation for shrinking and out of memory), the slightly extra memory usage of 1 size_t shouldn't be too much of a problem.
  • free_sptr, a wrapper around free.
  • malloc_sptr, a wrapper around malloc.
  • realloc_sptr, a wrapper around realloc.
  • calloc_sptr, a wrapper around calloc.
  • sptr_size which retrieves the size of the allocated memory.
  • rel_realloc_sptr which is like realloc, but relative to the current size. What type should I use for the relative size? I am currently using a long long int for the difference between two size_ts. Maybe ptrdiff_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 a int 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);
}
  • 1
    An intrusive structures a better option in my opinion. Using malloc/free properly is hard enough and now it is still harder as you have to be sure if a pointer is a special one. – Phil1970 Jul 9 '17 at 23:18

Unaligned memory accesses

Suppose sizeof(size_t) is 4 and you wanted to use this code to allocate an array of doubles. What will happen is that the length will get stored in the first four bytes and the array of doubles will be shifted by 4 bytes. This means that each double will now be improperly aligned because instead of being on an 8 byte boundary, each double will instead be on a 4 byte boundary. Depending on your machine's architecture, this could cause problems.

The same thing will happen for any type with an alignment larger than the alignment of size_t. It will also affect fields within structs.

To fix the problem, I would suggest that instead of reserving sizeof(size_t) bytes for the length, you reserve MAX_ALIGNMENT bytes instead. You can define MAX_ALIGNMENT to be whatever it needs to be for your system, such as 8 or 16. Note that malloc itself has a built-in alignment which is typically 8 for 32-bit systems and 16 for 64-bit systems, so if you can determine what your malloc's alignment is, you can use the same value.

  • OK I just learnt about memory alignment. Thank you! I thought of a new approach: An 8 byte size can start at any 8 byte interval, so none of the single items would fall between an 8-byte interval. So, I can also store the size of each item before hand as well, so realloc can take the new number of items instead of bytes, and sptr_length could return the number of items. This way, I avoid an arbitrary MAX_ALIGNMENT. Again, thank you! – Artyer Jul 10 '17 at 9:52

In addition to @JS1 fine answer with its very important concern about alignment issues:

Allow free_sptr(NULL). The standard library function allows free(NULL). Maintain functional equivalence with yours

void free_sptr(void* sptr) {
  if (sptr) { // add
    free(((size_t*) sptr) - 1);
  }
}

IMO, malloc_sptr(0) should be explicitly specified in the .h file as to the functionality. OP's code return a non-NULL pointer. An alternative it to return NULL. I recommend OP's approach, with documentation.


Pedantic code would detect overflow with code like size + sizeof(size_t)


The whole messing with size_t and signed long long math rel_realloc_sptr(void* sptr, long long int relative_size) relies on 1) LLONG_MAX > SZIE_MAX(something not specified by C) and 2) does not detect conversion problems with size_t new_size = new_ssize < 0 ? 0U : (size_t) new_ssize;. Suggest a new approach that only uses size_t math.


[Edit] Sample aligned allocation code which uses max_align_t: "an object type whose alignment is as great as is supported by the implementation in all contexts". C11 §7.19 2

#include <stddef.h>

union sptr_u {
  max_align_t ma;
  size_t      sz;
};

void* malloc_sptr(size_t size) {
  union sptr_u* sptr;
  assert(size <= SIZE_MAX - sizeof *sptr);
  sptr = malloc(sizeof *sptr + size);
  if (sptr == NULL) {
    return NULL;
  }
  sptr->sz = size;
  return sptr + 1;
}
  • I really need a signed type, as it can downsize or upsize. I could just do two seperate functions, one to add size, and one to subtract, and that would be fairly clear and use a size_t. The main benefit of this is that you can use most memory manipulation functions, and most things should allow you to pass a custom free/malloc/realloc/calloc function. Thank you for taking time to read and comment on my code! – Artyer Jul 10 '17 at 9:49
  • Pedantic code would also detect/handle the odd case: sizeof(sz) > sizeof(max_align_t) and sizeof(sz) % sizeof(max_align_t) != 0. – chux Jul 10 '17 at 12:48

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.