If I write:
int some_arr[4];
some_arr = {0, 1, 2, 3};
Then my compiler (in this case, GCC) will complain that I don't have an expression before {
. So I need to use a compound literal, fine:
int some_arr[4];
some_arr = (int[]){0, 1, 2, 3};
And now we see that I'm not allowed to assign a value to an array.
What?
I can "circumvent" this with something like memcpy(some_arr, (int[]){0, 1, 2, 3}, sizeof(int[4]))
, or by assigning to each element of some_arr
one-by-one (or through a loop.) I can't imagine that GCC is incapable of parsing the individual assignments from what I've wrote (a lazy compiler that doesn't care about the user could probably even do it in the pre-processor), so it seems to come down to "the standard said no." So why does the standard say this particular thing is off-limits?
I'm not looking for the language in the standard that says it's not allowed as much as I'm looking for the history lesson of how that part of the standard came to be.
From ISO/IEC 9899:1999 on assignment operator constrains
§6.5.16 An assignment operator shall have a modifiable lvalue as its left operand.
Then on modifiable lvalue
§6.3.2.1 A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.
Why not? probably because the array name decays to pointer to first element most probably.
However, an array assignment wrapped by a struct is allowed, as such:
//gcc 5.4.0
#include <stdio.h>
struct A
{
int arr[3];
int b;
};
struct A foo()
{
struct A a = {{1, 2, 3},10};
return a;
}
int main(void)
{
struct A b = foo();
for (int i=0; i<3; i++)
printf("%d\n",b.arr[i]);
printf("%d\n", b.b);
}
Yields
1
2
3
10
tl;dr:
because C decided that arrays decay to pointers, and hasn't provided a way for the programmer to avoid it.
Long answer:
When you write
int arr[4];
from that moment on, every time you use arr
in a dynamic context, C considers arr
to be &arr[0]
, namely the decay of an array to a pointer (see also here and here).
Therefore:
arr = (int[]){0, 1, 2, 3};
is considered to be
&arr[0] = (int[]){0, 1, 2, 3};
which cannot be assigned. A compiler could implement a full array copy using memcpy()
, but then C would have to provide a means to tell the compiler when to decay to a pointer and when not to.
Note that a dynamic context is different from a static context. sizeof(arr)
and &arr
are static context processed at compile time, in which arr
is treated as an array.
Likewise, the initializations
int arr[4] = {0, 1, 2, 3};
or
int arr[] = {0, 1, 2, 3};
are static context - these initializations happen when the program is loaded into memory, before it even executes.
The language in the standard is:
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
When an array is inside a struct, e.g.
struct s {
int arr[4];
};
struct s s1, s2;
Then again using s1.arr
is like &s1.arr[0]
, and it cannot be assigned.
However, while s1 = s2
is dynamic context, is not referencing the array. The compiler knows it needs to copy the full array, because it's part of the definition of the structure, and this assignment is generated implicitly. For example, if the compiler chooses to implement struct assignment using memcpy()
, the array is automatically copied.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With