On Sat, 11 Jul 2026, Martin Uecker wrote:
>
> This changes the C FE to use the same representation for zero-sized
> arrays as the C++ FE. After recent patches, this is relatively
> straight-forward and mostly removes special code for such types.
> But there is certainly some residual risk that there are unexpected
> consequences. There is also some minor semantic change, i.e. see
> gcc.dg/gnu-zero-array.c, where the two types would be incompatible
> (and this also caused two types to be generated in the ctf-array-2.c
> test), but the new behavior seems more correct to me and is also what
> clang does.
>
> The explicit language test in gimple-array-bounds.cc seems unfortunate,
> but preserves the existing differences in behavior for C vs C++ for
> the code. This now just becomes more obvious. I did not have time to
> look at this in more detail, but I hope this can be cleaned up in
> the future.
I'll note your fixup does not work with LTO (you could walk up
to a relevant decls TRANSLATION_UNIT_DECL and look at its language).
store-layout.cc uses
/* Make sure that an array of zero-sized element is zero-sized
regardless of its extent. */
if (integer_zerop (element_size))
length = size_zero_node;
/* The computation should happen in the original signedness so
that (possible) negative values are handled appropriately
when determining overflow. */
else
{
/* ??? When it is obvious that the range is signed
represent it using ssizetype. */
if (TREE_CODE (lb) == INTEGER_CST
&& TREE_CODE (ub) == INTEGER_CST
&& TYPE_UNSIGNED (TREE_TYPE (lb))
&& tree_int_cst_lt (ub, lb))
{
lb = wide_int_to_tree (ssizetype,
offset_int::from (wi::to_wide
(lb),
SIGNED));
ub = wide_int_to_tree (ssizetype,
offset_int::from (wi::to_wide
(ub),
SIGNED));
}
length
= fold_convert (sizetype,
size_binop (PLUS_EXPR,
build_int_cst (TREE_TYPE
(lb), 1),
size_binop (MINUS_EXPR, ub,
lb)));
}
/* ??? We have no way to distinguish a null-sized array from
an
array spanning the whole sizetype range, so we arbitrarily
decide that [0, -1] is the only valid representation. */
if (integer_zerop (length)
&& TREE_OVERFLOW (length)
&& integer_zerop (lb))
length = size_zero_node;
I think it would be nice for the consumer side to have some
abstraction in tree.{cc,h} to compute the size of a TYPE_DOMAIN.
Looking at build_range_type_1 I just realized we do not initialize
TYPE_UNSIGNED (itype), so all TYPE_DOMAIN types appear signed
(but usually with sizetype typed TYPE_MIN/MAX_VALUE) ... I think
we should add there
TYPE_UNSIGNED (itype) = TYPE_UNSIGNED (type);
I'll note we document (in generic.texi)
@item ARRAY_TYPE
Used to represent array types. The @code{TREE_TYPE} gives the type of
the elements in the array. If the array-bound is present in the type,
the @code{TYPE_DOMAIN} is an @code{INTEGER_TYPE} whose
@code{TYPE_MIN_VALUE} and @code{TYPE_MAX_VALUE} will be the lower and
upper bounds of the array, respectively. The @code{TYPE_MIN_VALUE} will
always be an @code{INTEGER_CST} for zero, while the
@code{TYPE_MAX_VALUE} will be one less than the number of elements in
the array, i.e., the highest value which may be used to index an element
in the array.
which is worded in a slightly confusing way, either TYPE_MIN_VALUE
is said to always be zero, or the TYPE_MAX_VALUE incorrectly refers
to "the number of elements".
I wonder if this should mention [0, -1] as prefered canonical way
to express a zero number of elements array domain.
That said, with respect to the stor-layout.cc snippet above I'd
always liked to have frontend use a signed domain type for [0, -1],
using an unsigned one [0, -1U] is ambiguous.
Thanks,
Richard.
> Bootstrapped and regression tested on x86_64.
>
>
>
> Change the representation of zero-sized array to [0, -1] as in the
> the C++ FE. Special cases can then be removed for ubsan, and the error
> message for ubsan is changed [*] to the more correct [0]. The patch
> avoids regressions for -Wzero-size-arrays in gimple-array-bounds.cc
> by removing the upper bound for such arrays for C. Unfortunately,
> doing this unconditionally (also for C++) would cause regressions in
> the other direction, so the existing discrepancies between C and C++
> for this warning code are not yet resolved.
>
> gcc/c-family/
> * c-attribs.cc (build_attr_access_from_parms): Adapt.
> * c-ubsan.cc (ubsan_instrument_bounds): Remove special code.
>
> gcc/c/
> * c-decl.cc (zero_length_array_type_p): Adapt.
> * (grokdeclarator): Remove special code.
> (c_build_array_type_zero_size): Adapt.
>
> gcc/
> * gimple-array-bounds.cc (array_bounds_checker::check_array_ref):
> Set upper bounds to zero for zero-sized C arrays.
>
> gcc/testsuite/
> * gcc.dg/debug/ctf/ctf-array-2.c: Types are now identical.
> * gcc.dg/ubsan/bounds-4.c: Adapt.
> * gcc.dg/ubsan/bounds-4b.c: Adapt.
> * gcc.dg/ubsan/bounds-4b.c: Adapt.
> * gcc.dg/ubsan/bounds-4c.c: Adapt.
> * gcc.dg/ubsan/bounds-4d.c: Adapt.
> * gcc.dg/gnu-zero-size.c: New test.
>
> diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
> index d668ab96630..4a8a4ea8c74 100644
> --- a/gcc/c-family/c-attribs.cc
> +++ b/gcc/c-family/c-attribs.cc
> @@ -6112,7 +6112,9 @@ build_attr_access_from_parms (tree parms, bool
> skip_voidptr)
> if (mval && TREE_CODE (mval) == INTEGER_CST)
> {
> char buf[40];
> - unsigned HOST_WIDE_INT n = tree_to_uhwi (mval) + 1;
> + unsigned HOST_WIDE_INT n = 0;
> + if (!integer_all_onesp (mval))
> + n = tree_to_uhwi (mval) + 1;
> sprintf (buf, HOST_WIDE_INT_PRINT_UNSIGNED, n);
> spec += buf;
> }
> diff --git a/gcc/c-family/c-ubsan.cc b/gcc/c-family/c-ubsan.cc
> index c70d4781bcf..1e21982ce7e 100644
> --- a/gcc/c-family/c-ubsan.cc
> +++ b/gcc/c-family/c-ubsan.cc
> @@ -434,13 +434,7 @@ ubsan_instrument_bounds (location_t loc, tree array,
> tree *index,
> tree bound = TYPE_MAX_VALUE (domain);
> if (!bound)
> {
> - /* Handle C [0] arrays, which have TYPE_MAX_VALUE NULL, like
> - C++ [0] arrays which have TYPE_MIN_VALUE 0 TYPE_MAX_VALUE -1. */
> - if (!c_dialect_cxx ()
> - && COMPLETE_TYPE_P (type)
> - && integer_zerop (TYPE_SIZE (type)))
> - bound = build_int_cst (TREE_TYPE (TYPE_MIN_VALUE (domain)), -1);
> - else if (INDIRECT_REF_P (array)
> + if (INDIRECT_REF_P (array)
> && is_access_with_size_p ((TREE_OPERAND (array, 0))))
> {
> bound = get_bound_from_access_with_size ((TREE_OPERAND (array, 0)));
> @@ -486,26 +480,17 @@ ubsan_instrument_bounds (location_t loc, tree array,
> tree *index,
> if (TYPE_DOMAIN (type2) != NULL_TREE)
> {
> tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (type2));
> - if (max == NULL_TREE)
> + if (TREE_CODE (max) == INTEGER_CST)
> {
> - /* C [0] */
> - if (COMPLETE_TYPE_P (type2)
> - && integer_zerop (TYPE_SIZE (type2))
> - && l == 3)
> - next = TREE_OPERAND (cref, 1);
> - }
> - else if (TREE_CODE (max) == INTEGER_CST)
> - {
> - if (c_dialect_cxx ()
> - && integer_all_onesp (max))
> + if (integer_all_onesp (max))
> {
> - /* C++ [0] */
> + /* [0] */
> if (l == 3)
> next = TREE_OPERAND (cref, 1);
> }
> else if (integer_zerop (max))
> {
> - /* C/C++ [1] */
> + /* [1] */
> if (l >= 2)
> next = TREE_OPERAND (cref, 1);
> }
> diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
> index 42d35a87a17..db8d6904584 100644
> --- a/gcc/c/c-decl.cc
> +++ b/gcc/c/c-decl.cc
> @@ -5542,9 +5542,8 @@ zero_length_array_type_p (const_tree type)
> {
> if (TREE_CODE (type) == ARRAY_TYPE
> && COMPLETE_TYPE_P (type)
> - && TYPE_DOMAIN (type) != NULL_TREE
> - && (TYPE_MAX_VALUE (TYPE_DOMAIN (type)) == NULL_TREE
> - || integer_all_onesp (TYPE_MAX_VALUE (TYPE_DOMAIN (type)))))
> + && integer_zerop (TYPE_SIZE (type))
> + && integer_all_onesp (TYPE_MAX_VALUE (TYPE_DOMAIN (type))))
> return true;
> return false;
> }
> @@ -7467,15 +7466,7 @@ grokdeclarator (const struct c_declarator *declarator,
> }
> }
>
> - if (integer_zerop (size) && !this_size_varies)
> - {
> - /* A zero-length array cannot be represented with
> - an unsigned index type, which is what we'll
> - get with build_index_type. Create an
> - open-ended range instead. */
> - itype = build_index_type (NULL_TREE);
> - }
> - else
> + if (!integer_zerop (size) || this_size_varies)
> {
> /* Arrange for the SAVE_EXPR on the inside of the
> MINUS_EXPR, which allows the -1 to get folded
> @@ -7549,7 +7540,8 @@ grokdeclarator (const struct c_declarator *declarator,
> "support flexible array members");
>
> /* ISO C99 Flexible array members are effectively
> - identical to GCC's zero-length array extension. */
> + similar to GCC's zero-length array extension,
> + but encoded with an empty upper bound. */
> if (flexible_array_member)
> itype = build_index_type (NULL_TREE);
> }
> @@ -7578,9 +7570,7 @@ grokdeclarator (const struct c_declarator *declarator,
> /* When itype is NULL, a shared incomplete array type is
> returned for all array of a given type. Elsewhere we
> make sure we don't complete that type before copying
> - it, but here we want to make sure we don't ever
> - modify the shared type, so we gcc_assert (itype)
> - below. */
> + it. */
> {
> addr_space_t as = DECODE_QUAL_ADDR_SPACE (type_quals);
> if (!ADDR_SPACE_GENERIC_P (as) && as != TYPE_ADDR_SPACE (type))
> diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> index 5199a352ecc..928f90ba086 100644
> --- a/gcc/c/c-typeck.cc
> +++ b/gcc/c/c-typeck.cc
> @@ -542,15 +542,9 @@ c_build_array_type_unspecified (tree type)
> tree
> c_build_array_type_zero_size (tree type)
> {
> - /* The GCC extension for zero-length arrays differs from
> - ISO flexible array members in that sizeof yields
> - zero. */
> - type = c_build_array_type (type, build_index_type (NULL_TREE));
> - type = build_distinct_type_copy (TYPE_MAIN_VARIANT (type));
> - TYPE_SIZE (type) = bitsize_zero_node;
> - TYPE_SIZE_UNIT (type) = size_zero_node;
> - SET_TYPE_STRUCTURAL_EQUALITY (type);
> - return type;
> + return c_build_array_type (type, build_range_type (ssizetype,
> + ssize_int (0),
> + ssize_int (-1)));
> }
>
> tree
> diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
> index 4038b4030c4..58a7e27fd72 100644
> --- a/gcc/gimple-array-bounds.cc
> +++ b/gcc/gimple-array-bounds.cc
> @@ -41,6 +41,7 @@ along with GCC; see the file COPYING3. If not see
> #include "attribs.h"
> #include "tree-pass.h"
> #include "gimple-range.h"
> +#include "langhooks.h"
>
> // Always use the current range query for the bounds checker.
> array_bounds_checker::array_bounds_checker (struct function *func)
> @@ -353,6 +354,15 @@ array_bounds_checker::check_array_ref (location_t
> location, tree ref,
> tree up_bound = array_ref_up_bound (ref);
> tree up_bound_p1 = NULL_TREE;
>
> + /* FIXME: The C FE used to represent zero-length arrays as arrays without
> + upper bound. To avoid regressions where -Wzero-length-bounds would then
> + become a -Warray-bound warning, remove the bound here. Unfortunately,
> + also doing this for C++ causes regressions where -Warray-bounds become
> + -Wzero-length-bounds. */
> + if (up_bound != NULL_TREE && integer_all_onesp (up_bound)
> + && !lang_GNU_CXX ())
> + up_bound = NULL_TREE;
> +
> /* Referenced decl if one can be determined. */
> tree decl = NULL_TREE;
>
> diff --git a/gcc/testsuite/gcc.dg/debug/ctf/ctf-array-2.c
> b/gcc/testsuite/gcc.dg/debug/ctf/ctf-array-2.c
> index 8bceb7a8377..7b83b802d83 100644
> --- a/gcc/testsuite/gcc.dg/debug/ctf/ctf-array-2.c
> +++ b/gcc/testsuite/gcc.dg/debug/ctf/ctf-array-2.c
> @@ -21,9 +21,8 @@
> /* { dg-do compile } */
> /* { dg-options "-O0 -gctf -dA" } */
>
> -/* { dg-final { scan-assembler-times "0x12000000\[\t \]+\[^\n\]*ctt_info" 2
> } } */
> -
> -/* { dg-final { scan-assembler-times "\[\t \]0\[\t \]+\[^\n\]*cta_nelems" 2
> } } */
> +/* { dg-final { scan-assembler-times "0x12000000\[\t \]+\[^\n\]*ctt_info" 1
> } } */
> +/* { dg-final { scan-assembler-times "\[\t \]0\[\t \]+\[^\n\]*cta_nelems" 1
> } } */
>
> static int b1[] = {};
>
> diff --git a/gcc/testsuite/gcc.dg/gnu-zero-array.c
> b/gcc/testsuite/gcc.dg/gnu-zero-array.c
> new file mode 100644
> index 00000000000..4481abf5b9a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/gnu-zero-array.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Wall -std=gnu23" } */
> +
> +int a[] = { };
> +int b[0];
> +
> +// should be compatible types
> +extern typeof(a) *p;
> +extern typeof(b) *p;
> +
> diff --git a/gcc/testsuite/gcc.dg/ubsan/bounds-4.c
> b/gcc/testsuite/gcc.dg/ubsan/bounds-4.c
> index d1580d36d13..0c5292de8e1 100644
> --- a/gcc/testsuite/gcc.dg/ubsan/bounds-4.c
> +++ b/gcc/testsuite/gcc.dg/ubsan/bounds-4.c
> @@ -2,7 +2,7 @@
> /* { dg-do run } */
> /* { dg-options "-fsanitize=bounds -fsanitize-recover=bounds" } */
> /* { dg-output "index 15 out of bounds for type 'int
> \\\[15\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> -/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[\\\*\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> +/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[0\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 16 out of bounds for type 'int \\\[15\\\]'" }
> */
>
> struct A { int a; int b[]; };
> diff --git a/gcc/testsuite/gcc.dg/ubsan/bounds-4a.c
> b/gcc/testsuite/gcc.dg/ubsan/bounds-4a.c
> index 412e5fd0445..b245b143064 100644
> --- a/gcc/testsuite/gcc.dg/ubsan/bounds-4a.c
> +++ b/gcc/testsuite/gcc.dg/ubsan/bounds-4a.c
> @@ -2,7 +2,7 @@
> /* { dg-do run } */
> /* { dg-options "-fsanitize=bounds -fsanitize-recover=bounds
> -fstrict-flex-arrays=0" } */
> /* { dg-output "index 15 out of bounds for type 'int
> \\\[15\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> -/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[\\\*\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> +/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[0\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 16 out of bounds for type 'int \\\[15\\\]'" }
> */
>
> #include "bounds-4.c"
> diff --git a/gcc/testsuite/gcc.dg/ubsan/bounds-4b.c
> b/gcc/testsuite/gcc.dg/ubsan/bounds-4b.c
> index 3ca4106c10b..e77f3987af2 100644
> --- a/gcc/testsuite/gcc.dg/ubsan/bounds-4b.c
> +++ b/gcc/testsuite/gcc.dg/ubsan/bounds-4b.c
> @@ -3,7 +3,7 @@
> /* { dg-options "-fsanitize=bounds -fsanitize-recover=bounds
> -fstrict-flex-arrays=1" } */
> /* { dg-output "index 15 out of bounds for type 'int
> \\\[15\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 2 out of bounds for type 'int
> \\\[2\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> -/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[\\\*\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> +/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[0\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 16 out of bounds for type 'int \\\[15\\\]'" }
> */
>
> #include "bounds-4.c"
> diff --git a/gcc/testsuite/gcc.dg/ubsan/bounds-4c.c
> b/gcc/testsuite/gcc.dg/ubsan/bounds-4c.c
> index 8f846d60011..5a770c9aede 100644
> --- a/gcc/testsuite/gcc.dg/ubsan/bounds-4c.c
> +++ b/gcc/testsuite/gcc.dg/ubsan/bounds-4c.c
> @@ -4,7 +4,7 @@
> /* { dg-output "index 15 out of bounds for type 'int
> \\\[15\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 1 out of bounds for type 'int
> \\\[1\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 2 out of bounds for type 'int
> \\\[2\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> -/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[\\\*\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> +/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[0\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 16 out of bounds for type 'int \\\[15\\\]'" }
> */
>
> #include "bounds-4.c"
> diff --git a/gcc/testsuite/gcc.dg/ubsan/bounds-4d.c
> b/gcc/testsuite/gcc.dg/ubsan/bounds-4d.c
> index b2d979fe8c1..d25cbd6c7bf 100644
> --- a/gcc/testsuite/gcc.dg/ubsan/bounds-4d.c
> +++ b/gcc/testsuite/gcc.dg/ubsan/bounds-4d.c
> @@ -2,10 +2,10 @@
> /* { dg-do run } */
> /* { dg-options "-fsanitize=bounds -fsanitize-recover=bounds
> -fstrict-flex-arrays=3" } */
> /* { dg-output "index 15 out of bounds for type 'int
> \\\[15\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> -/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[\\\*\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> +/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[0\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 1 out of bounds for type 'int
> \\\[1\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 2 out of bounds for type 'int
> \\\[2\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> -/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[\\\*\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> +/* { dg-output "\[^\n\r]*index 0 out of bounds for type 'int
> \\\[0\\\]'\[^\n\r]*(\n|\r\n|\r)" } */
> /* { dg-output "\[^\n\r]*index 16 out of bounds for type 'int \\\[15\\\]'" }
> */
>
> #include "bounds-4.c"
>
--
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald; (HRB 36809, AG Nuernberg)