views:

3604

answers:

5

I recently read that using flexible array members in C was poor software engineering practice. However, that statement was not backed by any argument. Is this an accepted fact?

(Flexible array members are a C feature introduced in C99 whereby one can declare the last element to be an array of unspecified size. For example: )

struct header {
    size_t len;
    unsigned char data[];
};
A: 

You meant...

struct header
{
 size_t len;
 unsigned char data[];
};

In C, that's a common idiom. I think many compilers also accept:

  unsigned char data[0];

Yes, it's dangerous, but then again, it's really no more dangerous than normal C arrays - i.e., VERY dangerous ;-) . Use it with care and only in circumstances where you truly need an array of unknown size. Make sure you malloc and free the memory correctly, using something like:-

  foo = malloc(sizeof(header) + N * sizeof(data[0]));
  foo->len = N;

An alternative is to make data just be a pointer to the elements. You can then realloc() data to the correct size as required.

  struct header
    {
     size_t len;
     unsigned char *data;
    };

Of course, if you were asking about C++, either of these would be bad practice. Then you'd typically use STL vectors instead.

Roddy
provided that you are coding on a system where STL is supported!
Airsource Ltd
C++ but no STL... That's not a pleasant thought!
Roddy
So the idea is that C is dangerous? :P
botismarius
Name one compiler that accepts zero-length arrays. (If the answer was GCC, now name another.) It is not sanctioned by the C standard.
Jonathan Leffler
I've worked in a C++ but no STL environment - we had our own containers which provided the commonly used functionality without the full generality of the STL iterator system. They were easier to understand and had good performance. However, this was in 2001.
pjc50
+1  A: 

It is an accepted "fact" that using goto is poor software engineering practice. That doesn't make it true. There are times when goto is useful, particualarly when handling cleanup and when porting from assembler.

Flexible array members strike me as one main use, off the top of my head, which is mapping legacy data formats like window template formats on RiscOS. They would have been supremely useful for this about 15 years ago, and I'm sure there are still people out there dealing with such things who would find them useful.

If using flexible array members is bad practice, then I suggest that we all go tell the authors of the C99 spec this. I suspect they might have a different answer.

Airsource Ltd
+5  A: 

The reason I would give for not doing it is that it's not worth it to tie your code to C99 just to use this feature.

The point is that you can always use the following idiom:

struct header {
  size_t len;
  unsigned char data[1];
};

That is fully portable. Then you can take the 1 into account when allocating the memory for n elements in the array data :

ptr = malloc(sizeof(struct header) + (n-1));

If you already have C99 as requirement to build your code for any other reason or you are target a specific compiler, I see no harm.

Remo.D
The last line should be ptr = malloc(sizeof(header) + n); where n is the length of the string and you use the 1 as terminating \0.
Peter Olsson
Thanks. I left the n-1 since it might not be used as a string.
Remo.D
use wouldn't care about the sign if it was for sure a string. Regarding this, n-1 is correct.
botismarius
The 'following idiom' is not fully portable, which is why flexible array members were added to the C99 standard.
Jonathan Leffler
I can say that using this approach does generates big problems. For example if you are using Secure CRT functions and you try to do a `strcpy(data, sometext)` you will get buffer underrun errors at runtime.
Sorin Sbarnea
@sorin not sure what you mean. The problem you're talking about is related to using strcpy() instead of strncpy() the question is about creating arrays that can grow.
Remo.D
@Jonathan. Sorry I don't get why this is not portable, could you clarify better?
Remo.D
Jonathan's point is echoed by the committee (or at least some members), but it contradicts the facts. When considered together, several other parts of the standard **require** the old `[1]` trick to work just as well as `[]` (aside from possibly wasting a few extra bytes of storage).
R..
+1  A: 

As a side note, for C89 compatibility, such structure should be allocated like :

struct header *my_header
  = malloc(offsetof(struct header, data) + n * sizeof my_header->data);

Or with macros :

#define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */
#define SIZEOF_FLEXIBLE(type, member, length) \
  ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] )

struct header {
  size_t len;
  unsigned char data[FLEXIBLE_SIZE];
};

...

size_t n = 123;
struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n));

Setting FLEXIBLE_SIZE to SIZE_MAX almost ensures this will fail :

struct header *my_header = malloc(sizeof *my_header);
You can call me Chuck
Overly complex and there's no benefit over using `[1]` for C89 compatibility, if it's even needed...
R..
A: 

I've seen something like this: from C interface and implementation.

  struct header {
    size_t len;
    unsigned char *data;
};

   struct header *p;
   p = malloc(sizeof(*p) + len + 1 );
   p->data = (unsigned char*) (p + 1 );  // memory after p is mine! 

Note: data need not be last member.

Nyan
Indeed this has the advantage that `data` need not be the last member, but it also incurs an extra dereference every time `data` is used. Flexible arrays replace that dereference with a constant offset from the main struct pointer, which is free on some particularly common machines and cheap elsewhere.
R..