1

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.

7
  • 2
    C99 doesn't define a behavior for Object o = {.size = 2, .array = {1, 2}};. You can't initialize a flexible member in C99, so if that works, it's a compiler extension. Commented Nov 9, 2022 at 21:00
  • 2
    How does this user defined size gets known to the compiler? If you just #define a size, then you can use that directly. Commented Nov 9, 2022 at 21:03
  • What I basically want is to abstract away to workings of the Object. Basically I want to create an alternative to the C++ std::array (but in C) were the user of the Array Object can decide how big the object is but without the need of dynamic memory Commented Nov 9, 2022 at 21:08
  • without dynamic memory allocation you would not be able to resize array. Commented Nov 9, 2022 at 21:10
  • 1
    Then why not define struct Object5 { uint8_t size, uint8_t array[5]; } and initialize it with {5} and so on for the different sizes? std::array<uint8_t, ...> ( with different sizes) are their own types in C++ too. Commented Nov 9, 2022 at 21:13

7 Answers 7

2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

This feels like it's closest to target. It'd be grand with some helping macro(s) too.
Nice very interesting approach compiles and seems to work for me too thank you!
Some subtle detail here is C17 6.7.2.1/3. This union may not be a member of an array. So if you for example did Object o[] = { &(union ...){ ... }, &(union ...){ ... } }; it would suddenly be a constraint violation. Better to avoid such peculiar corner cases of C. The common initial sequence rule is quite exotic too, apparently with shaky compiler support.
@Lundin, it would be an array of pointers: Object *o[]. The argument does not apply here.
1

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);
}

1 Comment

This also looks like a very clean and simple way to do it thank you too!
0

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));

Comments

0

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.

Comments

0
#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

1 Comment

Ok, I see that you've now added the extent to the array. Yes, that'll work and will require manually creating a new definition for each size.
0

First of all, whenever you find yourself with exotic requirements like this, please take a step back and see if you can't simplify the program instead. Using obscure macro hacks is always the last resort - and even then mostly just when maintaining old code.

That being said, the least obscure solution might be to create an unique typedef per object, based on variable name. And then declare a variable of that type in the same macro. Then it would be possible to use it anywhere.

One possible solution:

#define create_obj(name, ...)                        \
typedef struct                                       \
{                                                    \
  uint8_t size;                                      \
  uint8_t array[sizeof( (uint8_t[]){__VA_ARGS__} )]; \
} obj_##name;                                        \
                                                     \
obj_##name name =                                    \
{                                                    \
  .size = sizeof( (uint8_t[]){__VA_ARGS__} ),        \
  .array = { __VA_ARGS__ }                           \
}

A type with the variable name appended will be created. It will not use a flexible array member but a fixed size array. It will not need dynamic allocation, it will not need to rely on exotic corner-case rules or non-standard extensions.

If another member type than uint8_t would be used, then simply determine the array size with the usual trick:

sizeof((type[]){ __VA_ARGS__ }) / sizeof(type[0])


Example:

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

#define create_obj(name, ...)                        \
typedef struct                                       \
{                                                    \
  uint8_t size;                                      \
  uint8_t array[sizeof( (uint8_t[]){__VA_ARGS__} )]; \
} obj_##name;                                        \
                                                     \
obj_##name name =                                    \
{                                                    \
  .size = sizeof( (uint8_t[]){__VA_ARGS__} ),        \
  .array = { __VA_ARGS__ }                           \
}


create_obj(filescope_obj,1,2,3);

int main (void) 
{
  create_obj(local_obj,1,2,3,4,5);

  for(size_t i=0; i<filescope_obj.size; i++)
  {
    printf("%"PRIu8 " ", filescope_obj.array[i]);
  }
  puts("");
  for(size_t i=0; i<local_obj.size; i++)
  {
    printf("%"PRIu8 " ", local_obj.array[i]);
  }
}

Output:

1 2 3 
1 2 3 4 5 

Comments

0

It would be great if Compound Literals could be used for this:

Object o = { .size = 16, .array = (uint8_t[16]) { 0 } };
// Error: initializer element is not computable at load time

However such an array literal is - as usual with arrays - coerced to a pointer to its first element. So the type is actually uint8_t*, which cannot be used to initialise the .array. That trick only works for top-level arrays in GCC, like

uint8_t array[] = (uint8_t[16]) { 0 };

but that's not what we want, we want to put the array in an Object. You could write a normal array initialiser with as many bytes as you need:

Object o = { .size = 16, .array = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };

But that's too cumbersome and nobody wants to count them. Large arrays would need macro magic to do the repetition, no good.

There is however another GCC extension that helps with this: Designated Initialisers. Just like the .array designator for the Object struct, you can use index or even range designators to initialise array members, and thereby create an array of the desired size:

Object o = { .size = 16, .array = { [0 ... 15] = 0 } };

Notice this only works for global (or static) declarations though.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.