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.

Reply via email to