0

Is it possible to define a struct member to hold data of undefined type/size?

typedef struct my_data {
size_t size;
??? data;
} my_data

int main (void) {
my_data *toto;
void *test;

test = malloc(100);
toto = (my_data*)test;

Basically I would like for the size field to hold the first 8 bytes and for a second field to hold the rest? Is it possible to define a structure this way in C?

Ekid
  • 11
  • 2
  • You cannot have undefined type, but you can have an array of undefined size, like `char data[];` However you **cannot** use it with automatic variables like you've done in `my_data toto;`. You **must** use a pointer to heap-allocated data like in `my_data *toto = malloc (...);` – n. m. could be an AI Jul 24 '20 at 11:14
  • You can have an opaque pointer `struct`, see: https://stackoverflow.com/questions/7553750/what-is-an-opaque-pointer-in-c it looks like what you need. – Ilian Zapryanov Jul 24 '20 at 11:16
  • @n.'pronouns'm. Thanks you, Flexible length array is exactly what I need. – Ekid Jul 24 '20 at 12:22
  • @Ekid The question refers to undefined type, which suggests you wanted something more than a flexible array because that has a specified element type. – Ian Abbott Jul 24 '20 at 14:55

2 Answers2

0

Yes ! You can use Zero length array. It basically allows you to have a variable length array at the end of a structure. It works only if placed as the last element of the structure.

typedef struct my_data {
    size_t size;
    int data[0];
} my_data;

int main (void) {
    my_data *toto;
    void *test;

    test = malloc(sizeof(my_data) + sizeof(int) * 100);
    toto = (my_data*)test;
    toto->size = 100;
    toto->data[43] = 123;
    printf("%d\n", toto->data[43]);
    free(toto); //don't forget to free dynamically allocated memory
}

So this is what fits best your example. But if you simply want to store a variable length array, just use a pointer to a dynamically allocated array:

int main (void) {
    int *data;
    size_t size = 100;
    data = malloc(sizeof(int) * size);
    data[42] = 123;
    printf("%d\n", data[42]);
    free(data); //don't forget to free dynamically allocated memory
}
Puck
  • 2,080
  • 4
  • 19
  • 30
  • 2
    Zero-length array is a GCC extension. You can use a flexible array member that is part of the ISO C. – eanmos Jul 24 '20 at 11:42
0

The main problem to deal with is alignment. The pointer to the memory block returned by malloc is correctly aligned for any object type, but the pointer just past the end of the size member of your type may not be correctly aligned for some object types. You can work around that by using a union between the size member and some other object of type max_align_t:

typedef union {
    size_t size;
    max_align_t reserved__;
} my_data_header;

Then to allocate an object, add on the size of my_data_header, and use a pointer just past the end of my_data_header to get to point to your object:

my_data_header *head = malloc(sizeof(*head) + object_size);
if (head) {
    head->size = object_size;
    object_ptr = (void *)&head[1];
} else {
    object_ptr = NULL;
}

However to free the memory, you need to free the head, not the object pointer. Alternatively, the pointer to the head can be retrieved from the object pointer:

if (object_ptr) {
    head = &((my_data_head *)(void *)object_ptr)[-1];
} else {
    head = NULL;
}

A set of interface functions could be defined to allow objects of any size to be allocated and freed and the current size to be retrieved:

obj_alloc.c

#include "obj_alloc.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef union {
    size_t size;
    max_align_t reserved__;
} obj_hdr;

static obj_hdr *obj_to_hdr(void *obj)
{
    if (!obj) {
        return NULL;
    }
    return (obj_hdr *)obj - 1;
}

size_t obj_size(const void *obj)
{
    if (!obj) {
        return 0;
    }
    return obj_to_hdr((void *)obj)->size;
}

void *obj_reallocarray(void *obj, size_t nmemb, size_t size)
{
    obj_hdr *hdr = obj_to_hdr(obj);

    if (!(nmemb && size)) {
        /* New size is zero. */
        free(hdr);
        return NULL;
    }
    if ((SIZE_MAX - sizeof(*hdr)) / nmemb < size) {
        /* Too big */
        errno = ENOMEM;
        return NULL;
    }
    hdr = realloc(hdr, sizeof(*hdr) + nmemb * size);
    if (!hdr) {
        /* Allocation failure. */
        errno = ENOMEM;
        return NULL;
    }
    /* Record size and return pointer to object. */
    hdr->size = nmemb * size;
    return hdr + 1;
}

void *obj_allocarray(size_t nmemb, size_t size)
{
    return obj_reallocarray(NULL, nmemb, size);
}

void *obj_calloc(size_t nmemb, size_t size)
{
    void *obj = obj_reallocarray(NULL, nmemb, size);

    if (obj) {
        memset(obj, 0, nmemb * size);
    }
    return obj;
}

void *obj_malloc(size_t size)
{
    return obj_reallocarray(NULL, 1, size);
}

void *obj_realloc(void *obj, size_t size)
{
    return obj_reallocarray(obj, 1, size);
}

void obj_free(void *obj)
{
    free(obj_to_hdr(obj));
}

obj_alloc.h

#ifndef OBJ_ALLOC_H__INCLUDED
#define OBJ_ALLOC_H__INCLUDED

#include <stddef.h>

/**
 * \brief Get current size of object.
 *
 * Get current size of object allocated by obj_malloc(),
 * obj_realloc(), obj_calloc(), obj_allocarray(), or
 * obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to object, or null pointer.
 *
 * \return current size of object.
 */ 
size_t obj_size(const void *obj);

/**
 * \brief Free object.
 *
 * Free object allocated by obj_malloc(), obj_realloc(),
 * obj_calloc(), obj_allocarray(), or obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to object, or null pointer.
 */
void obj_free(void *obj);

/**
 * \brief Allocate an object from dynamic memory.
 *
 * Allocate an object of specified size from dynamic memory and
 * record its size.
 *
 * \param[in] size
 *   Size of object to be allocated.
 *
 * \return On success, returns a pointer to the allocated object,
 * or a null pointer if \p size is 0. On failure, returns a null
 * pointer and sets \c errno to \c ENOMEM.
 */
void *obj_malloc(size_t size);

/**
 * \brief Reallocate an object from dynamic memory.
 *
 * Reallocate an existing object to a new, specified size from
 * dynamic memory and record its new size.
 *
 * obj_realloc(NULL, size) behaves like obj_malloc(size).
 *
 * obj_realloc(obj, 0) behaves like obj_free(obj) and returns a
 * null pointer.
 *
 * If a pointer to an object is given, it must have been returned
 * by a previous call to obj_malloc(), obj_realloc(),
 * obj_calloc(), obj_allocarray(), or obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to existing object, or a null pointer.
 * \param[in] size
 *   New size of object to be reallocated.
 *
 * \return On success, returns a pointer to the reallocated
 * object, or a null pointer if \p size is 0. On failure, returns
 * a null pointer and sets \c errno to \c ENOMEM, but the original
 * object remains unchanged.
 */
void *obj_realloc(void *obj, size_t size);

/**
 * \brief Allocate a zeroed array of objects from dynamic memory.
 *
 * Allocate memory for an array of objects from from dynamic
 * memory and with a specified number of elements and a specified
 * element size, recording the total size, and setting the memory
 * contents to zero.
 *
 * obj_calloc(nmemb, size) is equivalent to
 * obj_malloc(nmemb * size) followed by
 * memset(ptr, 0, nmemb * size) if the return value is non-null.
 * The call is valid even if the multiplication of \p nmemb by
 * \p size would result in arithmetic overflow, but the function
 * will fail to allocate memory in that case.
 *
 * \param[in] nmemb
 *   Number of elements to allocate.
 * \param[in] size
 *   Size of each element.
 *
 * \return On success, returns a pointer to the allocated memory,
 * or a null pointer if \p size is 0 or \p nmemb is 0. On failure,
 * returns a null pointer and sets \c errno to \c ENOMEM.
 */
void *obj_calloc(size_t nmenb, size_t size);

/**
 * \brief Allocate an array of objects from dynamic memory.
 *
 * Allocate memory for an array of objects from from dynamic
 * memory and with a specified number of elements and a specified
 * element size, recording the total size.
 *
 * obj_allocarray(nmemb, size) is equivalent to
 * obj_malloc(nmemb * size).  The call is valid even if the
 * multiplication of \p nmemb by \p size would result in
 * arithmetic overflow, but the function will fail to allocate
 * memory in that case.
 *
 * \param[in] nmemb
 *   Number of elements to allocate.
 * \param[in] size
 *   Size of each element.
 *
 * \return On success, returns a pointer to the allocated memory,
 * or a null pointer if \p size is 0 or \p nmemb is 0. On failure,
 * returns a null pointer and sets \c errno to \c ENOMEM.
 */
void *obj_allocarray(size_t nmemb, size_t size);

/**
 * \brief Reallocate an array of objects from dynamic memory.
 *
 * Reallocate an existing object to an array of objects from
 * dynamic memory with a specified number of elements and a
 * specified element size, recording the new total size.
 *
 * obj_reallocarray(obj, nmemb, size) is equivalent to
 * obj_realloc(obj, nmemb * size).  The call is valid even if the
 * multiplication of \p nmemb by \p size would result in
 * arithmetic overflow, but the function will fail to reallocate
 * memory in that case.
 *
 * obj_realloc(obj, 0, size) and obj_realloc(obj, nmemb, 0)
 * behave like obj_free(obj) and return a null pointer.
 *
 * If a pointer to an object is given, it must have been returned
 * by a previous call to obj_malloc(), obj_realloc(),
 * obj_calloc(), obj_allocarray(), or obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to existing object, or a null pointer.
 * \param[in] nmemb
 *   Number of elements to allocate.
 * \param[in] size
 *   Size of each element.
 *
 * \return On success, returns a pointer to the reallocated
 * memory, or a null pointer if \p nmemb is 0 or \p size is 0.
 * On failure, returns a null pointer and sets \c errno to \c
 * ENOMEM, but the original object remains unchanged.
 */
void *obj_reallocarray(void *obj, size_t n_memb, size_t size);

#endif
Ian Abbott
  • 15,083
  • 19
  • 33