Home > OS >  C: Initialize custom array size in struct at compile time
C: Initialize custom array size in struct at compile time

Time:11-10

Consider the following code:

typedef struct Object
{
  uint8_t size;
  uint8_t array[];
}Object;

How would I make the Object with a compile time but user defined size array?

After reading some stackoverflow posts and trying the code myself I now know that as long as I use at least the compiler standard C99 I can declare the Object with a custom size like this:

Object o = {.size = 2, .array = {1, 2}};

But what I want is just an empty array with a custom size so instead of typing

Object o = {.size = 5, .array = {1, 2, 3, 4, 5}};

i want to type something like

Object o = {.size = 5, .array = uint8_t[5]};

What is also important is, that I need to know the array size at compile time and can't use dynamic memory like malloc so the typical solution of a flexible array member which is just allocated with the use of malloc wouldn't work for me.

Does a solution like this exist?

If it does not exist: Why would the first example work (for some compilers) but just telling the compiler I need a size 10 array in the struct at compile time without initializing the values in it would not?

Edit: I guess I should have made clear that my goal was to create something similar to the C std::array since with that datatype the user can just choose the size of the array without having to know the inner workings and rewriting the struct for every new size.

CodePudding user response:

You can use a union defined within a compound literal:

Object *o = &(union { Object o;
                      // anonymous struct
                      struct { uint8_t size; uint8_t array[N]; };
                    } ) { .size = N }.o;

It can be quite well packed into a macro.

It compiles without a warning in pedantic mode producing the expected results for both global and stack-allocated object. See https://godbolt.org/z/jhsbMseY8

This solution is guaranteed to work because in C the two struct members of the union can alias as long as they share common initial subsequence.

CodePudding user response:

I suggest that you define a type for each size of Object that you want.

You could simplify it by defining some macros:

#include <stdint.h>
#include <stdlib.h>

#define OBJINIT(x) (Object##x){.size = x }
#define DEFOBJ(x)     \
typedef struct        \
{                     \
  uint8_t size;       \
  uint8_t array[x];   \
} Object##x;          \
Object##x *CreateObject##x() {          \
    Object##x *rv = malloc(sizeof *rv); \
    if(!rv) exit(1);                    \
    *rv = OBJINIT(x);                   \
    return rv;                          \
}

// Define the types with the sizes you need:
DEFOBJ(2)
DEFOBJ(5)

int main(void) {
    Object2 o2 = OBJINIT(2);         // an initialized automatic variable
    
    Object5* o5p = CreateObject5();  // an initialized dynamically allocated variable
    free(o5p);
}

CodePudding user response:

The normal way to allocate the size of an array like this so you can easily change it and use the size of it elsewhere in the code is to use a macro.

#define MYARRAYSIZE 10

typedef struct 
{
  uint8_t size;
  uint8_t array[MYARRAYSIZE];
}Object_t;

The other way to do is to make it a pointer and then allocate the memory dynamically at runtime with malloc. Much bigger arrays can be declared this way.

#define MYARRAYSIZE 10

typedef struct 
{
  uint8_t size;
  uint8_t *array;
}Object_t;
 ...
Object_t Object;
Object.array = (uint8_t *)malloc(MYARRAYSIZE*sizeof(uint8_t));

CodePudding user response:

If one uses dialects that extend the semantics of the language by relaxing the "strict aliasing rule" sufficiently to uphold the Spirit of C principle "Don't prevent the programmer from doing what needs to be done", a common approach is to do something like:

struct polygon { int sides; int coords[]; };

struct poly5 { int sides; int coords[10]; }
  const myPentagonn = {5, {0,-5, 4,-3, 3,5, -3,5, -4,-3} };

#define myPentagon (*((struct polygon*)&myPentagonn))

In pre-standard dialects, it would often have been necessary to either declare polygon as containing a zero-element array, or if compilers decided to be annoying, a single-element array, but there was never any doubt about whether functions should be able to act upon multiple similar structure types interchangeably as exemplified here. Such constructs, with a few adjustments, were recognized in the 1974 dialect of C, and dialects suitable for low-level programming will support them without regard for whether or not the Standard would mandate such support.

Some clang/gcc fans who don't accept the intentions of the authors of the Standard as expressed in the published Rationale documents will insist that any code which would require the use of -fno-strict-aliasing is broken. The intention of the Standard, however, was to treat support for such constructs as a quality-of-implementation issue outside the Standard's jurisdiction. An implementation which is intended for tasks that would never need nor benefit from any kind of low-level memory manipulation might be more efficient for such tasks than would be one that accommodates such needs, but that doesn't mean code that is intended to work with memory at a low level should jump through hoops to be compatible with such specialized implementations or configurations.

CodePudding user response:

#include <stdio.h>
#include <stdint.h>

typedef struct Object
{
  uint8_t size;
  uint8_t array[5];
} Object;

int main() {
    Object o = { .size = 2,  {1, 2}};
    printf("%d %d\n", o.array[0], o.array[1]);
}

This compiles for me with cc -std=c99 in clang 14.0.0 on macOS. It outputs:

1 2
  • Related