-2

I am trying to figure out how much space i need to allocate when I am using a flexible array member. I have the following

typedef struct _A
{
    struct other;
    MyStruct params[1];
} A;

typedef struct _B
{
    int size;
    unsigned char values[1];
} B;

typedef struct _MyStruct
{
    int one;
    int two;
    int three;
} MyStruct;

WHat I want to do: fill out MyStruct, create a struct B, and add them both to struct A.

So I Have the following:

MyStruct test;
test.one = 1;
test.two = 2;
test.three  3;

unsigned char *input = (unsigned char *) malloc(arraysize);

//fill up input with data...then
B *testB = malloc(sizeof(*testB) + arraysize);
testB->size = arraysize;
//put input into the structure
memcpy(testB->values, input, arraysize);

//Here I want to add both test, and testB to a struct A - what do I need to do here?

A *testA = malloc(SOME SIZE HERE?)
testA->other=NULL;
testA->params[0]= ????
testA->params[1]=???

And yes, I know that the two structs I want to add to params are not the same, but I am going to cast them at a later point.

Derek
  • 11,715
  • 32
  • 127
  • 228
  • 5
    You have no flexible array members here, did you mean `MyStruct params[];` in `struct _A`? – Kninnug Aug 14 '15 at 17:47
  • How will you access the combined structure? – wallyk Aug 14 '15 at 17:47
  • 1
    I guess `sizeof(struc _A + N*sizeof(MyStruct)); ` – Severin Pappadeux Aug 14 '15 at 17:47
  • Downvoted: question mentions flexible array member, code in question has no flexible array members. – davmac Aug 14 '15 at 17:50
  • 1
    @Kninnug I suppose this is C89, which didn't have "flexible array member" in the standard, so you'd have to specify array size 1 for the code to compile. I'm not 100% sure if it is undefined behaviour or not (assuming not indexing outside what was allocated with `malloc`). – hyde Aug 14 '15 at 17:50
  • 2
    @davmac such a terminology mistake/ambiguity is kinda flimsy reason to downvotetrivial, considering c tag covers also c89, for which the kind of code in the question was/is common practice. – hyde Aug 14 '15 at 17:57
  • possible duplicate of [How can a mixed data type (int, float, char, etc) be stored in an array?](http://stackoverflow.com/questions/18577404/how-can-a-mixed-data-type-int-float-char-etc-be-stored-in-an-array) – Kuba hasn't forgotten Monica Aug 14 '15 at 17:59
  • 1
    @hyde: The phrase "flexible array member" has clearly been introduced with C99. It did not exist in previous C standards. Using it for something clearly different **is** a reason for downvote. Especially as a simple google search for this phrase would have shown many examples for the correct usage. – too honest for this site Aug 14 '15 at 18:02
  • 2
    @Olaf I didn't say it's not a reason, I said it is flimsy reason. Anyway, if a downvoter bothers to coment, it would be polite to at least suggest better terminology, instead of just saying "you're doing it wrong, -1" . – hyde Aug 14 '15 at 18:12
  • 1
    @hyde: Oh, the term is right. And as OP does not state it is **not** C99 or C11, I have to assume it is - even more as C11 is the **only** valid standard. So He apparently used the wrong approach (which is actually UB), showing not enough research effort: Clear reason to DV. (Note: your edit changed the meaning of the code. OP has enough rep to know how to ask and edit himself. Apparently he is not that much interested in a proper answer - fire and forget question). – too honest for this site Aug 14 '15 at 18:26
  • 1
    “struct hack” would've been a or even the correct term, by the way. – cremno Aug 14 '15 at 18:26
  • I corrected the typos. Someone edited to take out the [1] from the array notation. I have put that back in as it is in the code. – Derek Aug 14 '15 at 18:47
  • @Olaf please oh enlightened one, tell me the correct term, when it is in the code as I have described it, and functional at present. I am here to understand what is happening. – Derek Aug 14 '15 at 18:49
  • @Derek: I think the torch of the enlightened is cremno's. That is a hack with UB as of C90 which just happens to work for most implementations. How do you call a simple array with a single element? Array? C99 provides a clean and defined approach with _flexible array members_. – too honest for this site Aug 14 '15 at 18:56
  • I make it a point to not call other people's functional code "hacks" when there is probably a better term. – Derek Aug 14 '15 at 19:00
  • @hyde if the terminology was corrected I'd reverse the downvote. I don't consider that "flimsy" at all. – davmac Aug 14 '15 at 20:43
  • 3
    @Derek: I'm not sure I fully understand your comment but your code makes use of the so-called [struct hack](http://www.open-std.org/JTC1/SC22/WG14/www/docs/n791.htm). Don't use it or simply accept this term. – cremno Aug 14 '15 at 21:12
  • Oh my ... discussing about the term "hack" is silly. It's common practice in C89 because, although not defined, it would be quite an effort for an implementation to make it actually *break*. The definition in C99 is nothing more than making some previously overlooked use-case well-defined. And @Olaf, again, **no**, a newer standard does not *invalidate* the older ones. –  Aug 14 '15 at 22:16
  • @FelixPalmen: Right. But you should be better [informed](https://en.wikipedia.org/wiki/ANSI_C#C99). Or just read the [official statement](http://www.iso.org/iso/catalogue_detail.htm?csnumber=29237). And no, the behaviour is apparently _not_ the same, as you have to account for the 1 element for allocation. Even more, as C11 added bounds checking options which will make that not even work anymore. So, the term "hack" is a perfect description. – too honest for this site Aug 14 '15 at 22:18
  • @Olaf what are you after? withdrawal of a document? That doesn't make it *invalid* at all. The first organization to standardize C wasn't ISO, I guess you know that... –  Aug 14 '15 at 22:23
  • @FelixPalmen: It assume it was Pizza Hut? The withdrawan does make it non-authoritative. But quite interesting that you do not even mention the more important part of my comment. Well, I think that leads nowhere. G'nite! – too honest for this site Aug 14 '15 at 22:37
  • @Olaf, this is not for discussion and your edit wasn't visible until finishing my comment. And if you stop thinking of older standards as automatically invalid, it's not a huge concern. There are good reasons that standards-compliant compilers tend to support all previous standards, too. –  Aug 14 '15 at 22:41
  • Just for completeness sake, I was able to get code that works that answers my initial question. I will post it up soon. – Derek Aug 15 '15 at 03:52

1 Answers1

1

This will not work. Why? Because A has a fixed layout:

sizeof(other)
sizeof(MyParams)
sizeof(MyParams)
     ...

The things that can fit in MyParams slots cannot be bigger than MyParams. So, in general, the variable size B won't fit there. You also have a problem of alignment: if you append items immediately after one another, you're ignoring the alignment requirements on your platform. So the simple appending without padding for alignment will not work.

You really seem to wish for A to be a collection of objects of arbitrary size. In most general way, you can simply store an array of universal (void*) pointers:

typedef struct {
  Other other;
  void ** params;
} A;

You can then toss "anything" into params:

void test(void) {
  A a;
  a.params = (void**)malloc(sizeof(void*) * 3);
  a.params[0] = malloc(sizeof(MyStruct));
  ((MyStruct*)a.params[0])->one = 1;
  ...
  a.params[1] = malloc(sizeof(B) + arraysize);
  ((A*)a.params[1])->size = arraysize;
  ...
  a.params[2] = NULL; // you have to somehow indicate the last item
}

To deallocate, iterate params until you get a null element:

void destroyA(A * a) {
  void ** p;
  for (p = a->params; *p; ++p) free(*p);
  free(a->params);
}

Alas, you have no indication what's really stored in each of the params pointers - so it's hard to use them safely.

To be a bit more type safe, you should have a discriminated (tagged) union to represent the "universal" elements.

enum AnyTag { BTag = 0, MyParamsTag = 1 };

typedef struct {
  ...
} B;

typedef struct {
  ...
} MyParams;

typedef struct {
  AnyTag tag;
  B d;
} TaggedB;

typedef struct {
  AnyTag tag;
  MyParams d;
} TaggedMyParams;

typedef union {
  AnyTag tag;
  TaggedB b;
  TaggedMyParams myParams;
} Any;

typedef struct {
  Other other;
  Any ** params;
} A;

Then it's much easier to ensure that you access the members correctly:

void destroyA(A * a) {
  Any ** p;
  for (p = a->params; *p; ++p) free(*p);
  free(a->params);
}

void test(void) {
  A a;
  a.params = (Any**)malloc(sizeof(AnyTag*) * 3);

  a.params[0] = (Any*)malloc(sizeof(TaggedMyParams));
  a.params[0]->myParams.tag = MyParamsTag;
  a.params[0]->myParams.d.one = 1;
  ...
  a.params[1] = (Any*)malloc(sizeof(TaggedB) + arraysize);
  a.params[1]->b.tag = BTag;
  a.params[1]->b.d.size = arraysize;
  memcpy(a.params[1]->b.d.values, input, arraysize);
  ...
  a.params[2] = NULL;
  ...
  process(&a);
}

void processMyParams(MyParams *);
void processB(B *);
void process(A * a) {
  Any ** param = a->params;
  for (Any ** param = a->params; *param; ++param) {
    switch(param->tag) {
    case MyParamsTag:
      process(&param->myParams.d);
      break;
    case BTag:
      process(&param->b.d);
      break;
    }
  }
}

If you're willing to put the tags into the base types B and MyParams themselves, a trick shown here you can get rid of the d so that things are easier to type:

enum AnyTag { BTag = 0, MyParamsTag = 1 };

typedef struct {
  AnyTag tag;
  ...
} B;

typedef struct {
  AnyTag tag;
  ...
} MyParams;

typedef union {
  AnyTag tag;
  B b;
  MyParams myParams;
} Any;

typedef struct {
  Other other;
  Any ** params;
} A;

And:

void test(void) {
  A a;
  a.params = (Any**)malloc(sizeof(AnyTag*) * 3);

  a.params[0] = (Any*)malloc(sizeof(TaggedMyParams));
  a.params[0]->myParams.tag = MyParamsTag;
  a.params[0]->myParams.one = 1;
  ...
  a.params[1] = (Any*)malloc(sizeof(TaggedB) + arraysize);
  a.params[1]->b.tag = BTag;
  a.params[1]->b.size = arraysize;
  memcpy(a.params[1]->b.values, input, arraysize);
  ...
  a.params[2] = NULL;
  ...
  process(&a);
}
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • I appreciate the completeness of this answer, but I think you answered after someone edited my original question. This is a good reference, and probably a better way to go for someone who is looking to write something from scratch. Alas I am modifying existing code. – Derek Aug 15 '15 at 03:53
  • @Derek I've looked at the edit history and nothing substantial has changed. The existing code can be reused, you must simply ignore the fact that `params` has any particular type and cast it to a pointer-to-Any array. You'll simply store an array of pointers there. I mean, come on, you already store completely unrelated junk there, so might as well stop pretending that it is a `MyParams` array - it isn't. It can only be one if `MyParams` constitute the first elements only (so that it starts with 0 or more MyParams elements, and then is followed by other types). – Kuba hasn't forgotten Monica Aug 15 '15 at 16:06