Zero size arrays in C
labbott.name
labbott.name
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.
> 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.
x.c:7:10: error: flexible array member not at end of structThe reason for this is that ... I don't use flexible array member myself or the [0] GCC extension, either.
Flexible array member doesn't seem to bring anything to the table other than formal definedness of behavior. I can't imagine any implementation which supports flexible-array member in its C99+ modes, which makes [1] fail in the same modes or in the C99 mode. In any old compiler that doesn't have C99 support, that formally undefined struct hack is all you have.
I don't mind using offsetof(type, member) to obtain the size of the header before the array, rather than sizeof(type).
The classic hack is portable to C90 and C++98, making it good for "Clean C".
That said, flexible array member is elegant in the sense that it just uses an incomplete array type similarly to a file scope array declaration. Using [1] is a bit like while (1) instead of for (;;). I don't like the [1] in the struct hack, but I don't use C for its beauty.
If "everyone" is defined as "people using GCC."
C FAQ: [http://c-faq.com/struct/structhack.html]
"Despite its popularity, the technique is also somewhat notorious: Dennis Ritchie has called it ``unwarranted chumminess with the C implementation,'' and an official interpretation has deemed that it is not strictly conforming with the C Standard, although it does seem to work under all known implementations. (Compilers which check array bounds carefully might issue warnings.)"
EDIT: the FAQ actually refers to arrays of one element where GCC makes a big deal about saving memory by accepting zero-length arrays. But you are correct that this kind of cleverness wasn't limited to GCC ( https://blogs.msdn.microsoft.com/oldnewthing/20031212-00/?p=... , https://blogs.msdn.microsoft.com/oldnewthing/20040826-00/?p=... ).
[1] is the old school approach, which is still honored by compilers despite indexing past the first element technically being undefined behavior.
[0] is a GCC extension.
[] is C99.
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;
} /*
* 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
#endifAn 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/.)
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).
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.
You don't have to. C has flexible arrays in structs for this, and they actually work.
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.)
Your prize for pointing out that the usage of ANSI C is incorrect is: disappointment in humanity.
Fortunately, these days VS2013+ do an OK job of C99.
Any event, 1999 was 17 years ago.
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.
t_packet *packet = malloc(sizeof(*packet) + payload_size);I'd class that as a valid quibble, if my interpretation is correct, but a quibble nonetheless. The general intent is clear enough from the standard, I think: http://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p18
Further reading on the subject of undefined behaviour (apropos of nothing in particular): http://port70.net/~nsz/c/c11/n1570.html#3.4.3p1, http://robertoconcerto.blogspot.co.uk/2010/10/strict-aliasin..., https://groups.google.com/forum/#!msg/boring-crypto/48qa1kWi..., http://blog.metaobject.com/2014/04/cc-osmartass.html
template <int N>
struct ion_system_heap {
struct ion_heap heap;
struct ion_page_pool *pools[N];
};
But that is not quite the same thing since ion_system_heap<10> and ion_system_heap<20> won't be the same type and makes it super hard to use. And a whole bunch of other problems like not knowing how many pages you want to allocate at compile time.Yes you can just use a vector for "pools" but that allocates it on the heap instead of in the struct.
It solved it by wrapping with a superclass that you downcast from. You won't be able to work with them by value due to object slicing but you can still have a vector of pointers:
vector<Panda_without_bamboo*> pandas;
I am not sure how to do the downcasting though. Assuming the classes look like this: public Panda_without_bamboo
{
int bambooCount;
}
template<std::size_t N>
class Panda_with_bamboo : public Panda_without_bamboo
{
int a;
int b;
Bamboo bamboo[N];
}
You can't really do: static_cast<Panda<pandas[0].bambooCount>*>(pandas[0])
So it does seem like using flexible array members (first answer in that stackoverflow and the topic discussed in this submission) is a cleaner solution if you need contiguous allocation: http://www.codeatcpp.com/2007/10/dynamic-array-template.htmlIf you need contiguous memory though this is likely a terrible way to go about it since you are not only having to dereference pointers that could go anywhere, but each one will mean a separate heap allocation which will be slow for small objects and not scale with concurrency.
Edit: But casting the allocated memory around to a struct type might violate aliasing rules...
uint16_t array[] = { 1, 2, 3, 4 }; cout << "Hello World " << 3[array] << endl;
are valid and well defined. Part of what makes memory access validation such a pain in the ass in C++ is that no attempts are really made at specifying rules for how you can come up with memory locations; just rules on which locations you can read and write from. You can do things like "((int)rand()) = 2", and nothing in the standard says this is an invalid thing to do, so long as you are lucky enough for rand() to point somewhere allocated.
Good times.
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.
x = malloc(sizeof(x) + <variable_length>);
x->ptr = x + sizeof(x);
See [0] for a picture.
struct{ int something; int someArray[]; }
and
struct{ int something; int someArray[0]; }
?
https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Zero-Length.htm...
Other compilers might have similar treatment for someArray[0].
edit: clarifying, I thought int somearray [] was the same as int* somearray.
/* file scope */
int a[];
/* later in same translation unit, or in another one: */
int a[42];
In this case, we get an "incomplete array type" after the first declaration. The storage size isn't known and so sizeof a cannot be computed.This is also a de facto kind of "flexible array", but not formally named such.
For instance we can do this:
/* In proprietary kernel source code */
/* Size deliberately unspecified */
extern struct driver driver_table[];
The proprietary kernel code is compiled into .o files and linked into a .a archive library.To complete the build, a driver table module must be added and some drivers. The module which defines driver_table can be open-source. It defines the size of driver_table by how many entries there actually are; users can configure as many or few drivers and re-build the kernel, without having to recompile the proprietary part in the .a archive.
Unixes were shipped like this once upon a time. The "device major number" is just the position in the device table.
¯\_(ツ)_/¯