Home > Software design >  C - How to declare an object type of arbitrary byte size?
C - How to declare an object type of arbitrary byte size?

Time:10-13

I want to loop over arrays in a type-independent manner by casting a void pointer to an array of objects of arbitrary byte size. I know the size of the array and the size of its items, but not the type of the items. Is there a safe and portable way of doing this?

All I can think of at the moment is to create a struct with one member, a char array of the required size. But I'm not sure how safe and portable this is as a solution, due to possibility of alignment issues and possibly other problems I haven't thought of.

Example code of what I want to do:

void myFunc(void * arrV, int len, size_t arrSize, size_t itemSize) {
  // type definition, for array item of size itemSize
  struct xxxx {char[itemSize];};
  struct xxxx * arr = (struct xxxx *) arrV;
  for (int i = 0; i < len; i  ) {
    if (i == 100) {
      struct xxxx arrItem = arr[i];
      // do something with arrItem;
    }
  }
}

Edit: question title edited as it was phrased incorrectly.

CodePudding user response:

Use malloc to allocate memory

void * itemArr = malloc(arrSize * itemSize);

malloc dynamically allocates memory equal to the size of its first parameter (in bytes).

Once you are done with using it, you can free that memory to make it usable again.

free(itemArr);

Edit: Ok here is trial 2

I do not think this:

struct xxxx {char[itemSize];};

is valid syntax in C, you need to have an identifier.

Also my argument still stands, if you want to create an object from an unknown type of arbitrary size, you can use malloc instead of a normal array. Use malloc, cast it to a void* or char*, and treat it as an unknown data type. A struct, or for that matter data types, are all abstractions. You can treat any piece of memory as a non-standard data type. Also as far as I know malloc shouldn't cause alignment issues as malloc pointers are always aligned

CodePudding user response:

The size of a structure has to be known at compile time. You can have a flexible array member (FAM and more FAM) in a structure, but that doesn't help, not least because the size of the structure doesn't include the size of the FAM. Your attempt to create a type struct xxxx with a size determined at run time is doomed.

The C standard has two functions that process arrays of arbitrary type and size: bsearch() and qsort(). They use void *base to point to the start of the array, size_t count to specify how many elements are in the array and size_t size to specify the size of each element:

void qsort(void *base, size_t count, size_t size,
           int (*compar)(const void *, const void *));

These functions both take a pointer to a function that compares two values in the array by whatever mechanism the function chooses.

To achieve your goal, you will need to emulate them. You've not specified how you expect to process the values in the array, and that would give you headaches unless you adopt a similar technique where you pass a pointer to each element of the array to a function.

You can process an array of arbitrary type and size with ordinary pointer arithmetic. Despite what the GCC compiler permits, Standard C says you can't do arithmetic on void * values, but you can do arithmetic on char * values. As long as the size of the items is the result of sizeof(TheActualType), you can step through data that is appropriately aligned. I'm assuming that in your interface, arrSize == len * itemSize (see also my comment), though that's barely critical as I redesign the interface to your function to more closely match the standard functions. I add a callback function which is given a void * argument that can be safely cast to whatever type is being processed:

void ProcessArray(void *base, size_t count, size_t size, void (*processor)(void *))
{
    char *array = base;
    for (int i = 0; i < len; i  )
    {
        (*processor)(array   i * itemSize);
    }
}

The function pointer should be of the type shown. Many people take the shortcut of defining a processing function such as void ProcessingFunction(SomeType *arg); and passing that to a function. That includes such prestigious references as Brian W Kernighan and Dennis M Ritchie The C Programming Language, 2nd Edn (1988). But the C standard says (in §6.3.2.3 (Conversions of) Pointers) that calling a pointer to a function with the wrong type is undefined behaviour. So, strictly, you should not pass such a function to a function like ProcessArray(). You need a function like:

void ProcessingFunction(void *arg)
{
    SomeType *ptr = arg;    /* Cast not necessary in C */
    …process data using ptr->member…
}

If need be, you can pass a 'context' pointer to the functions to convey information other than the element being processed to the processing function:

void ProcessArrayWithContext(void *base, size_t count, size_t size, void (*processor)(void *arg, void *context), void *context)
{
    char *base = arrV;
    for (int i = 0; i < len; i  )
    {
        (*processor)(base   i * itemSize, context);
    }
}

And then:

void ProcessingFunctionWithContext(void *arg, void *context)
{
    SomeType *ptr = arg;    /* Cast not necessary in C */
    ContextInfo *ctxt = context;
    …process data using ptr->member and ctxt->info…
}

There is precedent for this with the non-standard qsort_r() functions defined by Linux and BSD (macOS). However, be aware that the BSD and Linux interfaces to qsort_r() are different. The Linux version looks like:

void qsort_r(void *base, size_t nmemb, size_t size,
             int (*compar)(const void *, const void *, void *),
             void *arg);

The comparator takes the context as the third, non-const argument, and qsort_r() passes arg as that third argument. The BSD version looks like:

void qsort_r(void *base, size_t nel, size_t width, void *thunk,
             int (*compar)(void *, const void *, const void *));

The comparator takes the context as the first, non-const argument, and the context is passed before the pointer to the comparator.

In the Linux code, the function effectively contains:

void *v1 = …;
void *v2 = …;
int rc;
if ((rc = (*compar)(v1, v2, arg)) < 0)
    …
else if (rc > 0)
    …
else
    …

The BSD effectively contains:

void *v1 = …;
void *v2 = …;
int rc;
if ((rc = (*compar)(arg, v1, v2)) < 0)
    …
else if (rc > 0)
    …
else
    …

See also Pass extra parameter to comparator for qsort()?.

Obviously, the callback (processing) function could be given other types. For example, you might have it return a value of type int and the loop could exit early if the value returned is not 0, or if it is 0. The options are legion.

CodePudding user response:

I want to loop over arrays in a type-independent manner by casting a void pointer to an array of objects of arbitrary byte size. I know the size of the array and the size of its items, but not the type of the items. Is there a safe and portable way of doing this?

It depends on how portable you need it to be. Your suggestion of an defining a structure type containing an array of the desired length is not viable for two reasons:

  1. In standard C, structure types may not be defined as containing variable-length arrays, and

  2. There is no portable way to ensure that the structure type would not contain padding (which would make its size wrong).

Additionally, the variable-length array feature is optional in C11 and C17 (though it is a mandatory in C99, and some form of it might return to mandatory status in C2X). But if you are willing to rely on VLAs then you can use them more directly for your purpose:

void myFunc(void *arrV, int len, size_t arrSize, size_t itemSize) {
    char (*items)[itemSize] = arrV;  // pointer to (variable-length) array

    for (int i = 0; i < len; i  ) {
        // ... do something with items[i] ...
    }
}

If you don't want to depend on VLAs then the conventional way that functions such as qsort() do it is to convert the void * to a char * and perform appropriate arithmetic with that:

void myFunc(void *arrV, int len, size_t arrSize, size_t itemSize) {
    char *items = arrV;

    for (int i = 0; i < len; i  ) {
        char *item_start = items   (i * itemSize);
        // ... do something with *item_start
    }
}
  • Related