I'm seeing some code like this:
int foo()
{
int sz = call_other_func();
char array[sz];
/* whatever */
}
I'm baffled at how this would work and even compile with gcc. The size of the array is supposed to be static and determined at compile time, no?
This type of arrays are called variable length arrays (you would also like to raed: Arrays of Variable Length - GCC) and are allowed in C99 only. By using VLAs, the size of the array can be determine at runtime.
"In programming, a variable-length array (or VLA) is an array data structure of automatic storage duration whose length is determined at run time (instead of at compile time)." (Wikipedia)
They're supported in C99 (and subsequently in C11).
Read more about how it works: The New C: Why Variable-length Arrays?
This is valid C99 feature called variable length arrays(VLA), if you compile with gcc -std=c90 -pedantic you will receive the following warning:
warning: ISO C90 forbids variable length array ‘array’ [-Wvla]
using -std=c99 -pedantic will not produce a warning, although both gcc and clang support VLA outside of C99 mode and also in C++ which does not allow VLA as an extension.
We can see from the C99 draft standard section 6.7.5.2 Array declarators paragraph 4 says (emphasis mine):
If the size is not present, the array type is an incomplete type. If the size is * instead of being an expression, the array type is a variable length array type of unspecified size, which can only be used in declarations with function prototype scope;124) such arrays are nonetheless complete types. If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type.
Note that Visual Studio does not support VLA even though they now support C99
Related
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.
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 it. 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.
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 declared as a parameter to a function, the array gets adjusted to pointer type as per the passage you stated.
Strictly speaking, what the compiler is showing is correct, and in fact it's required to do so in order to be a strictly conforming implementation, however I'd have a hard time arguing that it makes sense for a compiler to reject a program that has char past_end[0] as a function parameter when it is equivalent to char *past_end.
An important thing to understand about the C89 Standard (which is also relevant when looking at subsequent versions) is that it involved compromises between the compile writers who didn't want to change the behavior of existing compilers that would reject certain constructs, and programmers who used other compilers that would accept those constructs, who didn't want to have to change their code.
In many such situations, the compromise that was reached was that the Standard would impose a constraint that would require that conforming compilers issue a diagnostic, but compilers whose customers would regard the constraint as silly would be free to accept the code after the diagnostic was issued. If the programmers would be satisfied with their code being "conforming", rather than "strictly conforming", they could then proceed to ignore the constraint if both they and the authors of their compiler thought it was silly.
There's a reason that the flag which enables warnings about zero-sized arrays is named "-pedantic". The authors of gcc recognized that the language would be better off without the constraint, but they provided an option to output a diagnostic in case it was violated, so as to satisfy the constraints demanded by pedants.
I'm dynamically calculating the size of an array. Something like:
void foo(size_t limit)
{
char buffer[limit * 14 + 1];
}
But just GCC compiler says:
error: ISO C90 forbids variable length array ‘buffer’
searching on SO I found this answer:
C99 §6.7.5.2:
If the size is an expression that is not an integer constant
expression... ...each time it is evaluated it shall have a value
greater than zero.
So, I did the re-declaration of size limit type variable to:
void foo(const size_t limit)
But it continues to give warning for me. Is this a GCC bug?
const-qualifying a variable doesn't make it a compile-time constant (see C99 6.6 §6 for the defintion of an integer constant expression), and before the introduction of variable-length arrays with C99, array sizes needed to be compile-time constants.
It's rather obvious that const-qualify a variable doesn't make it a compile-time constant, in particular in case of function parameters which won't be initialized until the function is called.
I see the following solutions to your problem:
compile your code as C99 via -std=c99 or -std=gnu99
allocate your buffer via malloc()
use alloca() if available, which is the closest you can come to variable-length arrays with C90
choose a maximum buffer size which is always used and fail if the given limit argument overflows
As a side note, even though C99 allows variable-length arrays, it's still illegal to use the value of an integer variable with static storage duration as size for an array with static storage duration, regardless of const-qualification: While there's nothing which prevents this in principle if the integer variable is initialized in the same translation unit, you'd have to special-case variables with visible defintion from those whose definition resides in a different translation unit and would either have to disallow tentative defintions or require multiple compilation passes as the initialization value of a tentatively defined variable isn't known until the whole translation unit has been parsed.
const does not introduce a constant in C but a read-only variable.
#define SIZE 16
char bla[SIZE]; // not a variable length array, SIZE is a constant
but
const int size = 16;
char bla[size]; // C99 variable length array, size is a constant
C90 doesn't allow variable length arrays. However, you can use the c99-gcc compiler to make this work.
You are compiling with c90-gcc but looking at C99 specifications.
No it is not a bug. You can't use a VLA in C90. When you declared
const size_t limit
that is not a constant expression. A constant expression would be something like a literal value 666.
Note that C differs significantly from C++ in this regard. Even a constant like this
const int i = 666;
is not a constant expression in C. This is the primary reason why constant values are typically declared with #define in C.
As written in your question, this is from C99, not C90, you need to compile it against C99 to be able to use variable length arrays.
A const qualified variable is not an integer constant expression in the sense of the standard. This has to be a literal constant, an enumeration constant, sizeof or some expression composed with these.
Switch to C99 if you may. The gcc option is -std=c99 (or gnu99 if you want gnu extension.)
int n=10;
int arr[n];
this code works fine in my GCC compiler. Isn't the size of static array is allocated at compilation time ? Shouldn't this code generate an error ?
Variable length arrays are a C99 feature(optional in C11) and gcc supports this as an extension when not in c99 mode, one quick way to see this with gcc is to use the following:
gcc -std=c89 -pedantic
You will see the following warning:
warning: ISO C90 forbids variable length array ‘arr’ [-Wvla]
but if you build using gcc -std=c99 -pedantic you will not see any warnings. From the C99 draft standard section 6.7.5.2 Array declarators paragraph 4:
[...] If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type.
I'm dynamically calculating the size of an array. Something like:
void foo(size_t limit)
{
char buffer[limit * 14 + 1];
}
But just GCC compiler says:
error: ISO C90 forbids variable length array ‘buffer’
searching on SO I found this answer:
C99 §6.7.5.2:
If the size is an expression that is not an integer constant
expression... ...each time it is evaluated it shall have a value
greater than zero.
So, I did the re-declaration of size limit type variable to:
void foo(const size_t limit)
But it continues to give warning for me. Is this a GCC bug?
const-qualifying a variable doesn't make it a compile-time constant (see C99 6.6 §6 for the defintion of an integer constant expression), and before the introduction of variable-length arrays with C99, array sizes needed to be compile-time constants.
It's rather obvious that const-qualify a variable doesn't make it a compile-time constant, in particular in case of function parameters which won't be initialized until the function is called.
I see the following solutions to your problem:
compile your code as C99 via -std=c99 or -std=gnu99
allocate your buffer via malloc()
use alloca() if available, which is the closest you can come to variable-length arrays with C90
choose a maximum buffer size which is always used and fail if the given limit argument overflows
As a side note, even though C99 allows variable-length arrays, it's still illegal to use the value of an integer variable with static storage duration as size for an array with static storage duration, regardless of const-qualification: While there's nothing which prevents this in principle if the integer variable is initialized in the same translation unit, you'd have to special-case variables with visible defintion from those whose definition resides in a different translation unit and would either have to disallow tentative defintions or require multiple compilation passes as the initialization value of a tentatively defined variable isn't known until the whole translation unit has been parsed.
const does not introduce a constant in C but a read-only variable.
#define SIZE 16
char bla[SIZE]; // not a variable length array, SIZE is a constant
but
const int size = 16;
char bla[size]; // C99 variable length array, size is a constant
C90 doesn't allow variable length arrays. However, you can use the c99-gcc compiler to make this work.
You are compiling with c90-gcc but looking at C99 specifications.
No it is not a bug. You can't use a VLA in C90. When you declared
const size_t limit
that is not a constant expression. A constant expression would be something like a literal value 666.
Note that C differs significantly from C++ in this regard. Even a constant like this
const int i = 666;
is not a constant expression in C. This is the primary reason why constant values are typically declared with #define in C.
As written in your question, this is from C99, not C90, you need to compile it against C99 to be able to use variable length arrays.
A const qualified variable is not an integer constant expression in the sense of the standard. This has to be a literal constant, an enumeration constant, sizeof or some expression composed with these.
Switch to C99 if you may. The gcc option is -std=c99 (or gnu99 if you want gnu extension.)
Why does neither gcc or clang generate an error when I try to compile code containing the following two lines?
int palindrome(char s[]){
char s2[strlen(s)];
I thought in such an instance you would have to dynamically allocate memory to s2.
GCC has an extension for this behavior, and it's also standard in C99, known as variable length arrays.
http://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html
Clang supports it due to GCC C and C99: http://clang.llvm.org/compatibility.html#vla
Section 6.7.5.2 Array declarators:
If the size is not present, the array type is an incomplete type. If
the size is * instead of
being an expression, the array type is a variable length array type
of unspecified size,
which can only be used in declarations with function prototype
scope) such arrays are
nonetheless complete types. If the size is an integer constant expression
and the element
type has a known constant size, the array type is not a variable
length array type;
otherwise, the array type is a variable length array type.
And an example from 6.5.3.4:
#include <stddef.h>
size_t fsize3(int n)
{
char b[n+3]; // variable length array
return sizeof b; // execution time sizeof
}
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf (not the standard, but a draft, and free. :)