https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127939
Bug ID: 127939
Summary: Arrays of structs ending in a zero-length array /
flexible array member
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c
Assignee: unassigned at gcc dot gnu.org
Reporter: pascal_cuoq at hotmail dot com
Target Milestone: ---
Arrays of structs ending in a zero-length array / flexible array
member are undocumented, not deprecated, not diagnosed by
-Wflex-array-member-not-at-end, yet compiled exactly like the deprecated
"not at the end of another structure" case.
See also: PR 68489 (C++ front end: arrays of flexible array members are
silently accepted), PR 96346 (missing warning accessing an element of a
non-trailing zero length array)
1. Background: what the manual says
-----------------------------------
Section "6.2.2 Arrays of Length Zero" of the GCC manual
(https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html ) quotes the ISO C
constraint and then claims a GCC extension for both halves of it:
"A structure containing a flexible array member, or a union containing
such a structure (possibly recursively), may not be a member of a
structure or an element of an array. (However, these uses are
permitted by GCC as extensions, see details below.)"
The "details below" only ever describe the member-of-a-structure half:
"The GCC extension accepts a structure containing an ISO C99 flexible
array member, or a union containing such a structure (possibly
recursively) to be a member of a structure."
followed by the two situations (last field / not the last field), the
statement for the latter that
"accessing a member of the array mid_flex.flex_data.data[] might have
undefined behavior. Compilers do not handle such a case consistently."
and
"Please use the warning option -Wflex-array-member-not-at-end to
identify all such cases in the source code and modify them. This
extension is now deprecated."
The element-of-an-array half announced by the parenthetical is never
described. It is neither documented as supported, nor as deprecated.
Option -Wflex-array-member-not-at-end does not report it. An array of
such structs appears once more in the section, in the paragraph on static
initialization ("struct foo d[1] = { { 1, { 2, 3, 4 } } }; // Invalid."),
but that example only says that the flexible array of an element cannot
be given a non-empty initializer; it says nothing about whether the
declaration is supported or what accesses to d[0].y mean. The only
sentence that can be read as covering the array case is the general one
about zero-length arrays:
"Declaring zero-length arrays in other contexts, including as interior
members of structure objects or as non-member objects, is discouraged.
Accessing elements of zero-length arrays declared in such contexts is
undefined and may be diagnosed."
That the compiler treats the two cases alike is deliberate. PR 96346
comment 0 quotes the rule from the GCC sources; it is still there, in
what is now array_ref_flexible_size_p() in gcc/tree.cc (commit
4520a26b9ba):
https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/tree.cc;hb=4520a26b9ba46a7f7c59265c8d26f9d2ed761ecd#l13301
/* If we have a multi-dimensional array we do not consider
a non-innermost dimension as flex array if the whole
multi-dimensional array is at struct end.
Same for an array of aggregates with a trailing array
member. */
else if (TREE_CODE (ref) == ARRAY_REF)
return false;
The test case below confirms that both cases get the same
-Wzero-length-bounds wording ("interior zero-length array") and both
receive the same inconsistent code, where a function's return value
contradicts the store the same function just performed. This happens
with -fno-strict-aliasing, so it is not a type-based aliasing issue. I
am not reporting this as wrong code, since the manual can be read as
declaring such accesses undefined. The request is that the array case be
documented, deprecated and diagnosed on the same footing as the
struct-member case, since it has the same problems.
2. Test case (zero-length array)
--------------------------------
File fam_array.c:
#include <stdio.h>
struct flex { int n; int a[0]; };
/* struct with a flexible array member (FAM) as an element of an array */
struct flex arr[2] = { { 1 }, { 2 } };
/* struct with a FAM as an interior member of a struct */
struct mid { int m; struct flex f; int k; };
struct mid g = { 0, { 1 }, 2 };
__attribute__((noinline))
int array_loop(int n) {
arr[1].n = 2;
for (int j = 0; j < n; j++)
arr[0].a[j] = 42;
return arr[1].n;
}
__attribute__((noinline))
int struct_loop(int n) {
g.k = 2;
for (int j = 0; j < n; j++)
g.f.a[j] = 42;
return g.k;
}
int main(void) {
int r1 = array_loop(1);
printf("array_loop(1) returned %d, arr[1].n is now %d\n", r1, arr[1].n);
int r2 = struct_loop(1);
printf("struct_loop(1) returned %d, g.k is now %d\n", r2, g.k);
return 0;
}
sizeof(struct flex) is 4, so arr[0].a[0] and arr[1].n are the same int,
and g.f.a[0] and g.k are the same int. The two functions are the same
code modulo "array element" versus "struct member".
3. Results
----------
Compiler: gcc version 16.2.0 (Compiler-Explorer-Build-gcc--binutils-2.44)
Target: x86_64-linux-gnu
Compiler Explorer link: https://godbolt.org/z/bzqcGTsYY
$ gcc -O0 fam_array.c && ./a.out
array_loop(1) returned 42, arr[1].n is now 42
struct_loop(1) returned 42, g.k is now 42
$ gcc -O1 -fno-strict-aliasing fam_array.c && ./a.out
array_loop(1) returned 2, arr[1].n is now 42
struct_loop(1) returned 2, g.k is now 42
$ gcc -O2 -fno-strict-aliasing fam_array.c && ./a.out
array_loop(1) returned 2, arr[1].n is now 42
struct_loop(1) returned 2, g.k is now 42
Assembly, -O1 -fno-strict-aliasing. In both functions the loop stores 42
at the address of the field that is then returned as the constant 2
(arr+4 is arr[1].n, g+8 is g.k):
array_loop:
movl $2, arr+4(%rip)
testl %edi, %edi
jle .L2
movl $arr, %eax
movslq %edi, %rdi
leaq arr(,%rdi,4), %rdx
.L3:
movl $42, 4(%rax)
addq $4, %rax
cmpq %rdx, %rax
jne .L3
.L2:
movl $2, %eax
ret
struct_loop:
movl $2, g+8(%rip)
testl %edi, %edi
jle .L6
movl $g, %eax
movslq %edi, %rdi
leaq g(,%rdi,4), %rdx
.L7:
movl $42, 8(%rax)
addq $4, %rax
cmpq %rdx, %rax
jne .L7
.L6:
movl $2, %eax
ret
The constant index form (arr[0].a[0] = 42; instead of the loop) is
compiled consistently: the gcc version above prints 42 in all four
places at -O1 and -O2 -fno-strict-aliasing.
4. Diagnostics
--------------
gcc 16.2.0 (Compiler Explorer: https://godbolt.org/z/bsf9rhMPr ):
$ gcc -O2 -fno-strict-aliasing -Wall -Wextra -pedantic \
-Wflex-array-member-not-at-end -c fam_array.c
fam_array.c:3:26: warning: ISO C forbids zero-size array 'a' [-Wpedantic]
3 | struct flex { int n; int a[0]; };
| ^
fam_array.c: In function 'array_loop':
fam_array.c:16:17: warning: array subscript 2147483646 is outside the bounds
of an interior zero-length array 'int[0]' [-Wzero-length-bounds]
16 | arr[0].a[j] = 42;
| ~~~~~~~~^~~
fam_array.c:3:26: note: while referencing 'a'
3 | struct flex { int n; int a[0]; };
| ^
fam_array.c: In function 'struct_loop':
fam_array.c:24:14: warning: array subscript 2147483646 is outside the bounds
of an interior zero-length array 'int[0]' [-Wzero-length-bounds]
24 | g.f.a[j] = 42;
| ~~~~~^~~
fam_array.c:3:26: note: while referencing 'a'
3 | struct flex { int n; int a[0]; };
| ^
Observations:
- -Wzero-length-bounds uses the identical "interior zero-length array"
wording for the array-element case and for the struct-member case.
- -Wflex-array-member-not-at-end is silent for both, because the
trailing array is spelled [0] rather than [].
- The declaration "struct flex arr[2]" itself produces no diagnostic,
not even with -pedantic (only the [0] in the struct definition does).
5. The same program with a C99 flexible array member
----------------------------------------------------
Replacing "int a[0];" by "int a[];" in fam_array.c:
- gcc 16.2.0 prints 42 in all four places at -O0, -O1 and -O2
-fno-strict-aliasing. The inconsistent code only occurs with the [0]
spelling.
- gcc 16.2.0, -O2 -fno-strict-aliasing -Wall -Wextra -pedantic
-Wflex-array-member-not-at-end (Compiler Explorer, with execution:
https://godbolt.org/z/1YxzaMerr ):
fam_array.c:6:13: warning: invalid use of structure with flexible array
member [-Wpedantic]
6 | struct flex arr[2] = { { 1 }, { 2 } };
| ^~~
fam_array.c:9:33: warning: invalid use of structure with flexible array
member [-Wpedantic]
9 | struct mid { int m; struct flex f; int k; };
| ^
fam_array.c:9:33: warning: structure containing a flexible array member is
n
ot at the end of another structure [-Wflex-array-member-not-at-end]
fam_array.c: In function 'array_loop':
fam_array.c:16:17: warning: array subscript j is outside array bounds of
'int[]' [-Warray-bounds=]
16 | arr[0].a[j] = 42;
| ~~~~~~~~^~~
fam_array.c:3:26: note: while referencing 'a'
3 | struct flex { int n; int a[]; };
| ^
Both declarations get the same -Wpedantic message, which says that
the use is invalid but not why. Only the struct-member case is also
reported by the dedicated option, although the array-element case is
listed in the very same sentence of the standard and has the same
storage problem (the flexible array of element i overlaps element
i+1). Conversely, the access arr[0].a[j] gets a -Warray-bounds
warning, while the identical access g.f.a[j] in struct_loop gets
none, so neither option covers both cases.
6. Suggestions
--------------
A. Document the element-of-an-array case in "Arrays of Length Zero",
so that the parenthetical "these uses are permitted by GCC as
extensions, see details below" is actually followed by details for
it. Deprecate it on the same footing as the not-at-end
struct-member case. The reason given for the latter ("Compilers do
not handle such a case consistently") appears to apply, if the
sentence refers to the sort of example shown in this ticket.
PR 68489 comment 3 suggests that glibc has used, or still uses,
arrays of such structs, which argues against rejecting the
construct, but not against documenting it and warning on request.
B. Make -Wflex-array-member-not-at-end or a sibling option warn when
a structure containing a flexible array member is used as the
element type of an array.
C. Consider having that option also cover the GNU zero-length array
spelling [0] in both positions. Its documentation says "C99 flexible
array member", so its silence on [0] is as documented, but with
gcc 16.2.0, [0] is the spelling that produces the inconsistent code
shown above, and it is the one the option does not report in either
position. PR 68489 comment 6 mentions a [0]-terminated struct used
as a non-last member in glibc; that use was internal and was removed
in 2019, in response to the GCC 10 warning about it (glibc commit
70c6e15654928c603c6d24bd01cf62e7a8e2ce9b, BZ #25097,
https://sourceware.org/git/?p=glibc.git;a=commit;h=70c6e15654928c603c6d24bd01cf62e7a8e2ce9b
),
and warnings located in system headers are suppressed by default
anyway.
If, between -Wzero-length-bounds, -pedantic and -Warray-bounds,
suggestions B and C are judged to be bathwater, please do not throw out
the baby A with them: the manual currently announces details that it
does not contain.