Is it valid, according to ISO C (any version), to specify a zero-sized array parameter?
The standard seems ambiguous. While it's clear that zero-sized arrays are invalid, array function parameters are special:
C23::6.7.6.3/6:
A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.
As long as you don't use static
, the size specified between []
is effectively ignored. As I understand the quoted paragraph, the compiler isn't allowed to make any suppositions at all about the pointer.
So, the following code should be conforming, right?
void h(char *start, char past_end[0]);
#define size 100
void j(void)
{
char dst[size];
h(dst, dst size);
}
I use past_end[0]
as a sentinel pointer to one-past-the-end (instead of a size; it's much more comfortable in some cases). The [0]
clearly tells that it's one past the end, and not the actual end, which as a pointer, readers might confuse. The end would be marked as end[1]
, to be clear.
GCC thinks it's not conforming:
$ gcc -Wall -Wextra -Wpedantic -pedantic-errors -std=c17 -S ap.c
ap.c:1:26: error: ISO C forbids zero-size array ‘past_end’ [-Wpedantic]
1 | void h(char *start, char past_end[0]);
| ^~~
Clang seems to agree:
$ clang -Wall -Wextra -Wpedantic -pedantic-errors -std=c17 -S ap.c
ap.c:1:30: warning: zero size arrays are an extension [-Wzero-length-array]
void h(char *start, char past_end[0]);
^
1 warning generated.
If I don't ask for strict ISO C, GCC still warns (differently), while Clang relaxes:
$ cc -Wall -Wextra -S ap.c
ap.c: In function ‘j’:
ap.c:7:9: warning: ‘h’ accessing 1 byte in a region of size 0 [-Wstringop-overflow=]
7 | h(dst, dst size);
| ^~~~~~~~~~~~~~~~
ap.c:7:9: note: referencing argument 2 of type ‘char[0]’
ap.c:1:6: note: in a call to function ‘h’
1 | void h(char *start, char past_end[0]);
| ^
ap.c:7:9: warning: ‘h’ accessing 1 byte in a region of size 0 [-Wstringop-overflow=]
7 | h(dst, dst size);
| ^~~~~~~~~~~~~~~~
ap.c:7:9: note: referencing argument 2 of type ‘char[0]’
ap.c:1:6: note: in a call to function ‘h’
1 | void h(char *start, char past_end[0]);
| ^
$ clang -Wall -Wextra -S ap.c
I reported this to GCC, and there seems to be disagreement:
https://gcc.gnu.org/pipermail/gcc/2022-December/240277.html
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108036
Is there any requirements to comply with the same requirements as for arrays?
Moreover, if that proves to be true, what about the following?:
void f(size_t sz, char arr[sz]);
Is the compiler entitled, under strict ISO C mode, to assume that the array will always have at least one element, even if I didn't use static
, just because I used array syntax? If so, that would probably be a regression in the language.
CodePudding user response:
Is it valid, according to ISO C (any version), to specify a zero-sized array parameter?
The C standard is clear. C 2018 6.7.6.2 1, which is a constraints paragraph, says:
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…
Since it is a constraint, the compiler must issue a diagnostic message about. However, as with most things in C, the compiler may accept it anyway.
The adjustment of an array parameter to a pointer is irrelevant; that must come later, since a declaration of a parameter to a pointer cannot be adjusted until there is a declaration to adjust. So the declaration has to be parsed, and it is subject to the constraints of that.
The
[0]
clearly tells that it's one past the end, and not the actual end, which as a pointer, readers might confuse.
You might use it to tell human readers that, but it does not mean that to the compiler. [static n]
tells the compiler there are at least n
elements, and [n]
has even less meaning than that. It is valid to pass a subarray to a function—to pass a pointer into the middle of an array with the function intended to be used only to access a subset of the array reaching neither to the start or the end of the original array, so, even if [0]
were accepted, it would not necessarily mean the pointer is pointing to the end of the array. It would be valid to point anywhere into an array.
CodePudding user response:
This is an interesting corner case.
Section 6.7.6.2p1 of the C11 standard specifying a constraint for array declarators states:
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. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.
What you've shown constitutes a constraint violation which requires a warning / error and is considered undefined behavior. But at the same time, because you have an array declaration in a function prototype, the array gets adjusted to pointer type as per the passage you stated.
Strictly speaking, what the compiler is showing is correct, however I'd have a hard time arguing that it makes sense for a compiler to reject such a program.