1

The C99 standard allows the creation of flexible array members such as

typedef struct pstring {
  size_t length;
  char   string[];
} pstring;

This is then initialized with something like pstring* s = malloc(sizeof(pstring) + len). Is it permissible for len to be zero? It would seem consistent, and would be nice for saving space from time to time (probably not with the pstring example, of course). On the other hand, I have no idea what the following code would do:

pstring* s = malloc(sizeof(pstring));
s->string;

This also seems like the sort of thing which might work with one compiler and not another, or on one OS and not another, or on one day and not another, so what I really want to know is what the standard says about this. Is that malloc in the example code undefined behavior, or is it only the access to s->string which is invalid, or is it something else entirely?

Antal Spector-Zabusky
  • 36,191
  • 7
  • 77
  • 140
  • I believe this is valid, but I'm not 100% sure. In any case, it would be almost impossible to write an implementation where this doesn't work, even if it's not officially blessed by the standard. – R.. GitHub STOP HELPING ICE Feb 18 '11 at 05:14

2 Answers2

7

What you do is valid, but accessing s->string[0] or feeding s->string to any function accessing the data is invalid.

The C99 Standard actually says (§6.7.2.1):

struct s { int n; double d[]; };

...

struct s t1 = { 0 }; // valid

...

The assignment to t1.d[0] is probably undefined behavior, but it is possible that

sizeof (struct s) >= offsetof(struct s, d) + sizeof (double)

in which case the assignment would be legitimate. Nevertheless, it cannot appear in strictly conforming code.

Community
  • 1
  • 1
Zulan
  • 21,896
  • 6
  • 49
  • 109
  • Would it be legal to pass the address of t1.d to memcpy if the size argument is zero? – supercat Mar 09 '16 at 00:15
  • @supercat, I'm afraid this is not straight forward to answer. [Answer 1](http://stackoverflow.com/a/29851995/620382) and [answer 2](http://stackoverflow.com/a/5243068/620382) clearly decline the validity of this, whereas [this answer](http://stackoverflow.com/a/3751937/620382) and the third comment directed to your past self ;-) has a good argument that it is valid. – Zulan Mar 09 '16 at 08:30
  • Even if a "one-past" pointer were legit for memcpy (as I think it should be, since it is a *valid* pointer to a sequence of *zero* objects), that wouldn't imply that a zero-size FAM pointer would have to be regarded as a one-past pointer. I see no reason why any sane person in charge of the language, asked that direct question, should authorize a compiler to regard it as anything else, but that doesn't mean that's what the Standard actually says. – supercat Mar 09 '16 at 16:02
  • IMHO the standard does not describe that precisely. The one thing it says is that `dp = &(s2->d[0]); // valid` even though `*dp` is UB. I'm not totally sure if that implies that `dp` has a *valid values, as described in 7.1.4*. – Zulan Mar 09 '16 at 17:06
  • I wish the authors of the Standard would specify behavior in various corner cases like `memcpy(x,y,0)` for any x and y [also `memcpy(x,x,n)` in all cases where `memmove(x,x,n)` would be valid]. It would be hard to imagine non-contrived implementations where it would add cost, but making such thing UB means programmers have to waste code guarding against them. – supercat Mar 09 '16 at 17:12
1

At one time, Microsoft C allowed the following:

struct pstring {
  size_t length;
  char string[0];
};

And I used something like this to build a text editor. However, this was a long time ago and I know it was non-standard at the time. I'm not even 100% certain Microsoft still supports it.

The bottom line is that it depends on your compiler, and the current compile settings.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • 1
    char [0] is often used for non-c99 codes (e.g. the Linux kernel). – Zulan Feb 18 '11 at 05:54
  • IMHO, it's too bad compilers forbade the use of a zero size, since simply allowing such a thing, without special handling, would in many cases have yielded semantically-useful behavior. Using a single-element array for such purposes is ugly, and precludes optimization of pointer arithmetic when indexing a single-element array (if `FOO_SIZE` is defined to 1 and a structure `*it` contains `char foo[FOO_SIZE]`, a compiler should be able to replace `it->foo[expr]` with `it->foo[0]`, but such replacement would break code that was forced to use a single-element array instead of a zero-element one. – supercat Nov 16 '12 at 22:54
  • It still supports it but issues non-standard-extension warning. – EFraim Jun 12 '13 at 08:56