10 ms·
Zero size arrays in C
- jandrese 11y agoThat seemed pretty straightforward to me. A zero size array is better thought of as a pointer to that part of the struct, and if you put two back to back you get two pointers to the same spot. I'm not sure what that guy was expecting to happen. The usual reason to use them is when you have a protocol header with a variable length data portion. Putting the zero length array in the struct allows you easy access to the data while not changing the sizeof() the header so you can avoid a bit of pesky pointer math and make the code easier to read. Thinking about this some more, you could also use this as a ghetto form of union, but I'm not sure why you would want to. struct { char[0] theBytes; int theValue; }
- gohrt 11y agoThe guy was expecting a compiler error for bogus constructs, which C99 provides for flexible array member: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
- ryao 11y agoA zero sized array can only be used at the end of the struct. It only makes sense there.
- michaelmior 11y agoYou can use zero sized arrays anywhere in a struct. However, I think you can make the argument that they don't really make sense anywhere.
- mzs 11y agoFunny, I used a union once to port code when I found-out zero sized arrays were a gcc thing: /* * SC C Compiler 8.8.4f1 (part of MPW 3.3) does not support zero sized * arrays. I use this 'clever' trick involving the preprocessor that * guarantees everything is aligned appropriately. It is possible with * this that some space will be wasted between the header and the * payload if the a single element of the payload is larger than the * header, but that is not the case here. */ #if 0 struct state { mbhdr_t st_mb; /* Screen 1, Line 1 */ short st_serln; /* Screen 1, Line 2 */ short st_astln; unsigned char st_count; unsigned char st_unit; char st_qs[2]; char st_units; /* Screen 1, Line 3 */ avunit_t st_avms[0]; }; #else struct state { union { struct { mbhdr_t suh_mb; /* Screen 1, Line 1 */ short suh_serln; /* Screen 1, Line 2 */ short suh_astln; unsigned char suh_count; unsigned char suh_unit; char suh_qs[2]; char suh_units; /* Screen 1, Line 3 */ } su_header; avunit_t su_align[1]; } st_union[1]; }; #define st_mb st_union[0].su_header.suh_mb #define st_serln st_union[0].su_header.suh_serln #define st_astln st_union[0].su_header.suh_astln #define st_count st_union[0].su_header.suh_count #define st_unit st_union[0].su_header.suh_unit #define st_qs st_union[0].su_header.suh_qs #define st_units st_union[0].su_header.suh_units #define st_avms st_union[1].su_align #endif
- _kst_ 11y ago> A zero size array is better thought of as a pointer to that part of the struct, ... An array of any size is best thought of as an array. Arrays are not pointers. (See section 6 of the comp.lang.c FAQ, http://www.c-faq.com/. http://www.c-faq.com/.) A zero size array is best thought of as illegal (if you want portability) or as a compiler-specific extension (if you don't mind depending on a particular compiler, perhaps gcc).
- jandrese 10y agoTechnically correct, but using the zero length array trick creates clearer, more self documenting code. It's a tradeoff between readability and portability.
- _kst_ 10y ago> Technically correct, Or, as I prefer to say, "correct". > but using the zero length array trick creates clearer, more self documenting code. It's a tradeoff between readability and portability. Clearer compared to which alternative? I find C99-style flexible array members (defined with "[]") quite clear.
- theseoafs 11y ago> C makes it very easy to get things subtly wrong. Yet another item to add to your code review checklist. Better yet, don't use zero size arrays unless you really have to. You don't have to. C has flexible arrays in structs for this, and they actually work.
- TazeTSchnitzel 11y agoC99, yes. Earlier versions didn't have that.
- janci 11y agois there a difference between struct{ int something; int someArray[]; } and struct{ int something; int someArray[0]; } ?
- kazinator 11y agoYes; the latter one is the "flexible array member" which is supported in ISO C99 and later. The former is a constraint violation requiring a diagnostic.
- timmaxw 11y agoDid you mean the other way around? I think someArray[] is a flexible array member and someArray[0] is a constraint violation.
- sigjuice 11y agoYou are right. OP has it backwards. gcc treats someArray[0] almost the same as someArray[]. https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Zero-Length.html#Zero-Length https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Zero-Length.htm... Other compilers might have similar treatment for someArray[0].
- tavert 11y agoJust don't try to have both a zero sized array and a flexible array in the same struct. g++ 5 or earlier will compile that, but g++ 6 will not: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69550 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69550
- hughw 11y agoMy C is rusty. I would have thought the former declared a pointer in the second member of the struct, i.e. typically a 4- or 8- byte field. edit: clarifying, I thought int somearray [] was the same as int* somearray.
- 11y ago
- kazinator 11y agoA zero sized array declaration was a constraint violation (this requiring a diagnostic) in ISO C 90 in all contexts: even as a function parameter, where the identifier being declared is actually a pointer and not an array at all! C99 added the "flexible array member" feature: the last element of a struct can be an array of size zero: "As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member." [C99, 6.7.2.1 ¶16] In C90 code, the "struct hack" is implemented using an array of size [1] at the end of a struct. Some compilers allowed zero (like GNU C, I think) before C99. In any case, supporting a [0]-sized array member which is not the last member of a struct, without issuing a diagnostic, is non-conforming. The example given in the article with consecutive [0] arrays requires a diagnostic. By the way, the (apparently only?) advantage of the flexible array member is that we can use sizeof (type) to obtain the size of the structure just excluding the first element of the array. Whereas with the C90 style struct hack, we must use offsetof(type, last_member) so that we exclude [1] from the calculation: struct foo { /* ... */ int array[ZERO_OR_ONE]; }; /* Correct in C99, if ZERO_OR_ONE is 0 Incorrect in C90, (ZERO_OR_ONE can't be zero). */ size_t foo_plus_3_elems = sizeof (struct foo) + 3 * sizeof(int); /* Correct in C99 whether or not ZERO_OR_ONE is 0 or 1. Correct in C90 with ZERO_OR_ONE being 1. */ size_t foo_plus_3_elems = offsetof (struct foo, array) + 3 * sizeof(int); Here, "incorrect" means we calculate slightly more storage than needed, usually without any downside. "Correct in C90" means de facto correct, not that it was well-defined behavior. "Everyone" was doing it.
- comex 11y agoI think you made a small mistake: an "incomplete array type", as allowed by the clause you cited, is one with empty brackets (int foo[]). Per 6.7.5.2.4: > If the size is not present, the array type is an incomplete type. This is different from specifying the size as 0, which is still not allowed, per 6.7.5.2.1: > In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. Oh, and for the record, while you may be alluding to this in your last paragraph: another advantage of flexible array members is that indexing them is actually well-defined per the spec (if enough space has been allocated, of course), while AFAIK the spec has never added a special case for length-1 arrays, so accessing the "extra elements" is technically undefined behavior. However, common compilers like GCC do treat length 1 specially, as an exception to optimizations that generally assume you won't index out of bounds, so 'technically' really is 'technically' - it's not something that some random future version of GCC is likely to break your code if you use.
- adrianN 11y agoI'm reasonably sure that in C++ at least accessing an element outside the array is always UB. Is this legal in some sufficiently modern C?
- gdwatson 11y agoBecause it's a widely-used hack that predates optimizers' obsessive exploitation of undefined behavior, it's become part of the language's customary semantics and is supported everywhere I know of. C99 adds a feature called flexible array members. If the final member of a struct is an array with no length (blank rather than zero), you're allowed to use the array to access memory after the struct with the appropriate subscripts.
- _delirium 11y agoIt's not only an optimizer that could throw you off if you really are relying on overflowing an array's bounds. While C doesn't require bounds checks, it also doesn't prohibit them, so a safety-oriented compiler could insert bounds checks (except in the special case in structs you mention, which by the standard has to be allowed). That exists in the real world too; the Intel C compiler can insert bound checks, though they aren't enabled by default.
- malka 11y agobound checking would act okay I guess. You usually alloc such a struct this way : t_packet *packet = malloc(sizeof(*packet) + payload_size);
- joosters 11y agoThe struct would be stored in a larger area of memory that has previously been malloc'd (to allow room for the data that you want to put after the header). So long as accesses to the data are before the end of the malloc, it won't be out of bounds and so won't be undefined behaviour.
- deleted 11y ago
- deleted 11y ago[deleted]
- Paul_S 11y agoAnd this is why in 2016 I'm still stuck with ANSI C. First person to tell me ANSI C is not C89 anymore wins a prize! I love C but I can't think of any other language that failed to gain traction as it got updates. Maybe similar to failure of Python 3 I guess but even Python 3 is getting reasonable uptake.
- _kst_ 11y agoANSI C is not C89 anymore. Where's my prize? 8-)} The term "ANSI C" is still very commonly used to refer to the language described by the 1989 ANSI C standard. This usage is strictly incorrect, but too firmly entrenched to ignore. The 1990 ISO C standard describes the same language, and was officially adopted by ANSI, making the 1989 standard obsolete. The 1999 and 2011 ISO standards, both also officially adopted by ANSI, each officially made all earlier standards obsolete. If you want to refer to the language defined by the 1989 ANSI C standard (and described in K&R2), call it "C89" or "C90". C11 might get more traction than C99 did, since gcc defaults to C11 plus GNU extensions starting with release 6. (But Microsoft's C compiler is still behind the times.)
- Paul_S 11y agoAll the compilers I ever used in my professional career could do C99 but most companies limit themselves to ANSI C (good luck convincing anyone that the term is incorrect!) for "compatibility" reasons and it's got nothing to do with Microsoft (who uses their compilers for C anyway?). Your prize for pointing out that the usage of ANSI C is incorrect is: disappointment in humanity.
- to3m 11y agoI used to use VC++ to build C code sometimes. People write software in C, and if you use VC++ you might want to compile it. C and C++ are not the same, and the fixing-up process can be time-consuming and error-prone, even assuming the code didn't go full C99 with anonymous aggregates and designated initializers. Fortunately, these days VS2013+ do an OK job of C99.
- 0x0 11y agoI think you dropped this: \ ¯\_(ツ)_/¯
- ivanca 11y agoSome softwares just delete all backslashes instead of handling it properly, in this case it would be converting it to an HTML entity (\)
- hiimnate 11y agoWhat is the benefit of using a zero sized array over a pointer?
- amq 11y agoWhen using shm_open and mmap there hardly is a shorter solution.
- halayli 11y agoyou can do x = malloc(sizeof x + <variable_length)) x->zero_array points to the variable_length. The advantage is that you get contiguous memory access and do a single malloc. As opposed to: x = malloc(sizeof x) x->ptr = malloc(<variable_length>); x and ptr are not necessarily contiguous.
- hiimnate 11y agoBut can't you write it so that it is contiguous using a pointer?
- jmpeax 11y agoYou can x = malloc(sizeof(x) + <variable_length>); x->ptr = x + sizeof(x);
- imron 11y agoExcept it requires sizeof a pointer more memory and an extra assignment after the malloc, which is why people use the zero-sized array instead (note this comment was to explain for the grandparent why you would normally prefer a zero-sized array instead).
- imron 11y agoNo, because a pointer points to some location in memory, not whatever follows the memory after the location of the pointer. See [0] for a picture. [0] http://c-faq.com/aryptr/aryptr2.html http://c-faq.com/aryptr/aryptr2.html
- georgeecollins 11y agoThis brings back so many memories of programming in C / C++ with pointers. Good times.
- curiousgal 11y agoWhat are you up to now?
- known 10y agoIsn't the pointer a zero size array?