0

I am implementing some lookup data in C99 (as a C-Script in the Software PLECS).

I want to create an array of struct with one (later this will be three) array members that will have a different (but always known) length for each struct. I would like to pass the data directly as I declare the struct array.

Here is what I tried so far.

#include <stdlib.h>

struct SomeStruct{
    double a;
    double *b;
};

struct SomeStruct StructArray[3] = {
    [0].a = 1,
    [0].b = (double*)malloc(2*sizeof(double)),
    [0].b = {1.01, 2.01}, //obviously wrong

    [1].a = 2,
    [1].b = (double*)malloc(3 * sizeof(double)),
    [1].b = { 1.01, 2.01, 3.01, }, //obviously wrong

    [2].a = 3,
    [2].b = (double*)malloc(4 * sizeof(double)),
    [2].b = { 1.01, 2.01, 3.01, 4.01 }, //obviously wrong
};

Unfortunately my C is a little rusty and I cant figure out how to dereference the pointer inside the struct. *([N].b) does not work.

Is this even possible? Or is there maybe a more elegant soultion?

PS: The Data will be quite large, the struct array will have a length of about 200 and there will three arrays of up to length 400 in each of them. The code for this is generated as a header file from a MATLAB Script.

EDIT: Above has been solved by @Gilles, the following is still of interest

Here the alternative way, initializing at runtime. Why does the following bit not work?

#include <stdlib.h>

typedef struct {
    double a;
    double *b;
} SomeStruct;

SomeStruct StructArray[2]; //Sample struct

void initializeLUT() // Filling the Struct with Data
{
    StructArray[0].a = 1;
    StructArray[0].b = (double*)calloc(2, sizeof(double));
    StructArray[0].b = { 1.01, 2.01 }; // does not work

    StructArray[1].a = 2;
    StructArray[1].b = (double*)calloc(3, sizeof(double));
    *(StructArray[1].b) = { 1.01, 2.01, 3.01, }; //does not work either
}

3 Answers3

2

In a global variable, the initializer must be a constant. The reason the language is designed that way is that for global data, the initializer typically consists of data embedded in the program binary, rather than code that is executed at runtime. As a consequence, even if you packaged the call to malloc and the element values into a function, you still wouldn't be able to use that to initialize your data.

If you want to have a static initializer, decouple the initialization of the pointer from the initialization of the array it initially points to. Make the initial array global variables, that's the only way you'll get to specify an initializer for them.

double row1[] = { 1.01, 2.01 };
double row2[] = { 1.01, 2.01, 3.01 };
double row3[] = { 1.01, 2.01, 3.01, 4.01 };
struct SomeStruct StructArray[3] = {
    [0].a = 1,
    [0].b = row1,
    [1].a = 2,
    [1].b = row2,
    [2].a = 3,
    [2].b = row3,
};

If you wanted to use malloc because you wanted to free or resize the arrays at runtime, then initialize the pointers to NULL, and call malloc from your own initialization code (e.g. in main, or better, call malloc in a function init_my_module defined in the same file my_module.c where StructArray is defined, and call init_my_module from main). You'll probably still need to have global variables for the initial array contents.

Note that if the arrays had a fixed size, a better approach would be to make SomeStruct contain the arrays directly, rather than pointer. But this approach doesn't work for arrays of varying size: you can have a structure with a flexible array member, but you can't put that structure in an array, because array elements need to have a constant size.

Also note that you should probably store the array size somewhere (typically in a field of the structure).

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • Thank you for your input. Unfortunately the arrays will have different lengths, but they wont change at runtime. Since there wille be more than one array per structure i think i will have to use pointers instead of direct arrays. i will try some more with the ininitialization at startup. one issue that comes up all the time is that i dont seem to get the dereferencing of the array of struct pointer right, i might amend my original post with this problem. – Rolf Loewenherz Jun 27 '16 at 08:54
  • Sorry, adding Code to Comments was a bad idea. I edited my original Post instead. – Rolf Loewenherz Jun 27 '16 at 09:19
  • @RolfLoewenherz Regarding your edit, the syntax you used is only valid inside initializer, i.e. when the definition of the variable is combined with an assignment. To initialize at runtime, Intuitively speaking, the compiler wants an address to put the `{1.01, 2.01}` data; if that address is obtained via `malloc`, the compiler doesn't know it (this explains why it's more than a syntax issue and the language wasn't designed to cope with this). – Gilles 'SO- stop being evil' Jun 27 '16 at 18:34
0

Since the data is apparently dynamic, you need to initialize it in run-time. Rather than using designated initializers, I think you are looking for another C99 feature called "flexible array member", which allows a struct to have a variable-sized struct at the end, in a safe manner. Example:

#include <stdlib.h>
#include <string.h>

typedef struct 
{
  double a; // whatever this is supposed to be
  size_t size;
  double data[]; // flexible array member
} SomeStruct;


SomeStruct* SomeStruct_alloc (double a, size_t size, double data[size])
{
  SomeStruct* ss = malloc(sizeof(SomeStruct) + sizeof(double[size]));
  if(ss == NULL)
  {
    return NULL;
  }

  ss->a = a;
  ss->size = size;
  memcpy(ss->data, data, sizeof(double[size]));

  return ss;
}


int main()
{
  SomeStruct* arr[3];

  arr[0] = SomeStruct_alloc(1, 2, (double[2]){1.01, 2.01});
  arr[1] = SomeStruct_alloc(2, 3, (double[3]){1.01, 2.01, 3.01});
  arr[2] = SomeStruct_alloc(3, 4, (double[4]){1.01, 2.01, 3.01, 4.01});

  return 0;
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thank you for your help. Unfortunately i this will probably not work for me. I think a Flexible array member will only work as the last member of the struct, as long as it is the only flexible member of the struct and as long as the struct is neither in an array or used for other definitions. I will use multiple arrays as members of my struct and put the struct itself in an array as well, so the only option i see are pointers. in the struct. Gilles soultion for the initialization does work. Though i would like to understand more about runtime initialization options like yours. – Rolf Loewenherz Jun 27 '16 at 09:56
  • 1
    @RolfLoewenherz Then you use multiple structs inside the main struct, simple as that. – Lundin Jun 27 '16 at 10:02
  • Flexible array members don't work if they aren't the last element of the data structure (because the compiler has to be able to know the offset of what follows them). This means that you can't put them in another struct (except as the last field, which doesn't help if you want multiple FAM in the same struct tree) or in an array. – Gilles 'SO- stop being evil' Jun 27 '16 at 18:31
  • @Gilles You can't have completely arbitrary data. The data sizes and the number of items has to be known. Then you can allocate/realloc accordingly. It is kind of meaningless to discuss the appropriate data structure with so little info about the data though. – Lundin Jun 28 '16 at 05:57
0

Replace

StructArray[0].b = (double*)calloc(2, sizeof(double));
StructArray[0].b = { 1.01, 2.01 }; // does not work

with

{
  double* p = calloc(2, sizeof(double));
  if (!p) { perror("calloc"); exit(EXIT_FAILURE); };
  static const double arr[] = { 1.01, 2.01 };
  memcpy (p, arr, 2*sizeof(double));
  StructArray[0].b = p;
}

you might even define a macro to help you, maybe:

#define FILLME(Ix,...) do {                            \
   static const double arr[] = {__VA_ARGS__};          \
   double*p = calloc(sizeof(arr)/sizeof(double),       \
                     sizeof(double));                  \
   if (!p)                                             \
     { perror("calloc"); exit(EXIT_FAILURE); };        \
   memcpy (p, arr, sizeof(arr));                       \
   StructArray[ix].b = p;                              \
} while(0)

then use it as

FILLME(0, 1.01,2.01);

BTW, you'll probably better use flexible array members (always last in their struct)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Thank you @Basile Starynkevitch , this works perfectly! Now both ways of initializing are solved. I only hat to add (double*) in front of calloc to make it compile. – Rolf Loewenherz Jun 28 '16 at 07:50
  • In C99 or C11 you don't need (and you should not) cast `malloc`. In C++ you should, but actually you'll better use `new` than `malloc` – Basile Starynkevitch Jun 28 '16 at 08:08
  • Ah, that makes sense. I tested it quickly in C++, since i only have a very limited environment for coding C99 right now (PLECS C-Code Windows, it's horrible). I will keep that in mind. – Rolf Loewenherz Jun 28 '16 at 08:47