lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Le mer. 13 mai 2020 à 19:20, Massimo Sala <massimo.sala.71@gmail.com> a écrit :
I consider this article, written by Gnu C authors, enlightening on the topic:
       https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

I have to disagree to this statement made in that article: "Although the size of a zero-length array is zero, an array member of this kind may increase the size of the enclosing type as a result of tail padding."

Because I don't see which kind of tail padding would occur after a zero-length array (which does not change at all the size of existing enclosing type, except for aligning the first member of the zero-length array: this is not *trailing* padding, but *leading* padding, i.e. influences the offset of the 1st element that could be in the variable-size array; but when the zero-length array has base type "char", its alignment is 1 by definition, meaning that multiple members of type non-array char are just packed at successive offsets+1:

Wherever you declare "char a,b,c;" or "char a[3];", these members should be located identically with no extra padding between each char (and not even any leading padding before the first one, except padding *bits* if the previous members with specified bit-sizes (like "int a:1;") did not fully fit a full byte boundary (bits in that case may be padded at low or high order positions, the compiler may chose to align these bit-sized members in a conveniently aligned way so that these bits can be read/written from a single byte or word access before using bit masking and shifting operations (a declaration like "int a:15;" may align member a to a 16-bit position so that the 15 bits will be accessed in a single operation, and most likely will allocate them in the 15 low-order bits of the 16-bit word to avoid a 1-bit shifting). (note that these extra padding bits added by incomplete bitfields are ignored by sizeof() because they are always counted as if there was an additional hidden bitfield).

But this only influences the location (i.e. offset of the first element). a "char[0]" member will just be aligned to force its first element to start at a byte boundary.

In contrast, a compiler that sees a "char[4]" member declaration may know that this member has a size which is at least 4 bytes, and may opt to align the whole array so its first element will be aligned to a 32-bit boundary. But a "char[0]" has a sizeof zero: you can align it to any multiple of a byte/word boundary, it will remain zero.

As a consequence, I do not see any difference of the size and alignment of the enclosing type if it is terminated by an trailing char[0] member or if this member is absent.

Let's remember that the C standard has always stated that sizeof(char)==1, as well as sizeof(unsigned char)==1 and  sizeof(signed char)==1 ; it also warranties that sizeof(int:1) == 1 but says nothing about sizeof(int:2) or about the alignment of bitfields, as they may or may not be packed together in the same byte or word if they fit, or allocated to other bytes/words with some padding bits added implicitly between them.

As a consequence the declaration "int a:15, b:2, c:12;", declaring three bitfields for a usable total of 32 bits, may allocate more than 32 bits:
- "a" could be aligned to a 16-bit boundary but still using only 15 of the 16 bits: allocated size=2
- "b" could be aligned to a separate 8-bit boundary, but still using only 2 of the 8-bits (with a padding **before** it, for the unused 1 bit left after "a") : allocated size=1
- "c" could be aligned to a separate 16-bit boundary , but still using only 12 of the 16-bits (with a padding **before** it, for the unused 6 bits left after "b", plus the extra 8 bits required to align "c") : allocated size=3
- the total would have a total size of 45 bits, aligned to a 16-bit boundary (the alignment required for the first member "a") but with 3 trailing bits (unused after "c"); if the structure is terminated there, it has a total of 48 bits (6 bytes); if you create an array of such structure, array members would all be aligned to 16-bit boundaries, and members would spaced every 6 bytes (not 8 bytes because first member "a" of each structure is only aligned to 16-bit boundaries, but does not require 64-bits alignment).

The compiler may also opt for reordering bits to use unused bits or pack the structure, and could then allocate 32 bits, but at the price of a 32-bit access (plus shifting) needed for reading/writing the 2 bits of "b" (with additional masking instructions in all cases, unless the native instruction set has a bit-level addressing mode to avoid the additional shift/mask operations, which may still occur at hardware level if external memory or registers are only addressable at byte or word boundaries without extra cycles). This means that  "int a:15, b:2, c:15;" is not portable: if you put that into an union with a 32-bit integer, that integer may not contain all the bits for a,b,c and the value of this 32-bit integer is unpredictable.

Compilers have a way to force specfiic members to be aligned at specific boundary at start using extra declarators, or implicitly when using pragma packing directives for the declaration of whole structs.

In all cases, nothing happens related to the trailing bits that are just allocated to fit the previously sized members: adding a char[0] will not increase the size and will preserve the existing minimum alignment of 8-bit for the whole structure, independantly of the structure packing pragmas.

So there should not be any difference between a trailing "char[]" or "char[0]" member. Both should be flexible, except that "char[0]" cannot have any initializer, while "char[]" can be assigned with a variable number of initializers for a local type only, but never for any dynamic allocation (including array of such "flexible" type).








_______________________________________________
lua-l mailing list -- lua-l@lists.lua.org
To unsubscribe send an email to lua-l-leave@lists.lua.org