3

Why is it so that a struct can be assigned after defining it using a compound literal (case b) in sample code), while an array cannot (case c))?

I understand that case a) does not work as at that point compiler has no clue of the memory layout on the rhs of the assignment. It could be a cast from any type. But going with this line, in my mind case c) is a perfectly well-defined situation.

typedef struct MyStruct {
  int a, b, c;
} MyStruct_t;

void function(void) {
  MyStruct_t st;
  int arr[3];

  // a) Invalid
  st = {.a=1, .b=2, .c=3};
  // b) Valid since C90
  st = (MyStruct_t){.a=1, .b=2, .c=3};
  // c) Invalid
  arr = (int[3]){[0]=1, [1]=2, [2]=3};
}

Edit: I am aware that I cannot assign to an array - it's how C's been designed. I could use memcpy or just assign values individually.

After reading the comments and answers below, I guess now my question breaks down to the forever-debated conundrum of why you can't assign to arrays.

What's even more puzzling as suggested by this post and M.M's comment below is that the following assignments are perfectly valid (sure, it breaks strict aliasing rules). You can just wrap an array in a struct and do some nasty casting to mimic an assignable array.

typedef struct Arr3 {
  int a[3];
} Arr3_t;

void function(void) {
  Arr3_t a; 
  int arr[3];

  a = (Arr3_t){{1, 2, 3}};
  *(Arr3_t*)arr = a;
  *(Arr3_t*)arr = (Arr3_t){{4, 5, 6}};
}

So then what's stopping developers to include a feature like this to, say C22(?)

davidanderle
  • 638
  • 6
  • 12
  • Initialization is not the same as assignment. – Barmar Jun 10 '20 at 17:32
  • 2
    You can't assign to arrays in any situation. – Barmar Jun 10 '20 at 17:33
  • `void main(void)` should be `int main(void)`. – Keith Thompson Jun 10 '20 at 17:33
  • 1
    @KeithThompson you're missing the point... This was 1 line shorter and wanted to be brief... In embedded, bare metal situation this is perfectly fine. – davidanderle Jun 10 '20 at 17:34
  • @Barmar - Sorry, but this comment is also not constructive. As I stated in my question, I know it is not valid. Question is why is it not handled. – davidanderle Jun 10 '20 at 17:38
  • 2
    @davidanderle: It is constructive. C never has assignment for arrays, in any context. The fact that the right-hand side of the assignment is a compound literal rather than a named array object is irrelevant. – R.. GitHub STOP HELPING ICE Jun 10 '20 at 18:01
  • You could use `memcpy()` by using the address of the compound literal, just like you could with another array. – Barmar Jun 10 '20 at 18:02
  • Yes, i know I can, thank you. – davidanderle Jun 10 '20 at 18:04
  • 2
    To be clear: all 3 cases (a,b,c) are attempted _assignments_, not _initializaiton_ - and arrays are not assignable. – chux - Reinstate Monica Jun 10 '20 at 18:13
  • Arrays are initialze-able `int arr[3] = { [1] = 11, [0] = 22 }; // 22, 11, 0` – chux - Reinstate Monica Jun 10 '20 at 18:18
  • @chux-ReinstateMonica - I edited the question clarifying intialize vs assign. Thanks! – davidanderle Jun 10 '20 at 19:03
  • 1
    " why you can't assign to arrays." --> Shhh. Dirty little secret: you can assign arrays with [gcc 4.8 , -std=c89](https://stackoverflow.com/q/18412094/2410359). – chux - Reinstate Monica Jun 10 '20 at 20:34
  • 1
    @chux-ReinstateMonica - haha, this is a gem! :) – davidanderle Jun 10 '20 at 21:22
  • 1
    perhaps we could debate the legality of `struct a3 { int a[3]; }; *(a3 *)&arr = (a3){{1,2,3}};` – M.M Jun 10 '20 at 21:55
  • @davidanderle Are you using a freestanding implementation whose documentation says that `void main(void)` is a supported entry point? And how is it one line shorter? `void main(void)` is one character longer than `int main(void)`. – Keith Thompson Jun 11 '20 at 01:21
  • @KeithThompson - I was referring to the return 0; Without an operating system, the return value of the main doesn't matter. Consider this `void main(void) as a custom function I made up just to get to a point. Could've called it void KeithThompson(void); I stand by it – davidanderle Jun 11 '20 at 07:30
  • @M.M - This is brilliant! It never ceases to amaze me how much one can brutally squeeze out from simple C :) I included this in my question – davidanderle Jun 11 '20 at 07:49
  • @davidanderle I'd say it's undefined behaviour due to strict aliasing violation, unfortunately – M.M Jun 11 '20 at 08:42
  • 1
    @davidanderle For a hosted implementation, defining `void main(void)` causes your program to have undefined behavior *unless* the implementation specifically documents that it supports it. (Microsoft's implementation does so.) "Undefined behavior" means the standard says nothing about what the program does. For a freestanding implementation, the entry point is entirely implementation-defined, and might even not be called `main`. Are you using a freestanding implementation? Starting with C99, the `return 0;` can be safely omitted. – Keith Thompson Jun 11 '20 at 19:22
  • 1
    @davidanderle See also questions 11.12a and following in the [comp.lang.c FAQ](http://www.c-faq.com/). – Keith Thompson Jun 11 '20 at 19:22
  • @KeithThompson - Thanks, I actually was not aware of the omitting of `return 0` being allowed in certain cases on an OS. I come from a world where you can use [negative indices to address an array](https://m.9gag.com/gag/aGeowj5) :) – davidanderle Jun 12 '20 at 16:02

2 Answers2

5

C does not have assignment of arrays, at all. That is, where array has any array type, array = /* something here */ is invalid regardless of the contents of "something here". Whether it's a compound literal (which you seem to have confused with designated initializer, a completely different concept) is irrelevant. array1 = array2 would be just as invalid.

As to why it's invalid, at some level that's a question of the motivations/rationale of the C language and its design and unanswerable. However, mechanically, arrays in any context except the operand of sizeof or the operand of & "decay" to pointers to their first element. So in the case of:

arr = (int[3]){[0]=1, [1]=2, [2]=3};

you are attempting to assign pointer to the first element of the compound literal array to a non-lvalue (the rvalue produced when arr decays). And of course that is nonsense.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 1
    @DanielWalker A compound literal needs a type so it knows what the members refer to. – Barmar Jun 10 '20 at 18:11
  • 4
    It doesn't automatically assume that the type is the type of the target of the assignment. And it's *not* an initialization, it's an assignment. That's a big difference. – Barmar Jun 10 '20 at 18:12
  • Was that changed in a later version of C? In my code, which uses C99, I've got initializations like `struct Thing thing = {.a = 1, .b = 2};`. – Daniel Walker Jun 10 '20 at 18:12
  • @Barmar, ahhhh! Got it! – Daniel Walker Jun 10 '20 at 18:12
  • Thanks, I edited the question with the proper use of compound literals! – davidanderle Jun 10 '20 at 19:04
  • @EricPostpischil: Thanks. It's rather inconsequential since there's no way it could be valid either way, but I updated the answer for correctness. FWIW the language *could have* excepted LHS of `=` without breaking anything, but it still wouldn't change the outcome; it would just change the cause of the error. It could not have excepted the RHS without drastically altering the language. – R.. GitHub STOP HELPING ICE Jun 10 '20 at 19:21
  • Re “drastically altering the language”: Two changes suffice: (a) Arrays are not automatically converted when they are the left operands of assignments. Not a big change, just adding to the array conversion a rule already in lvalue conversion. (b) Either the right array operand of an assignment is not automatically converted when the left operand is an array, and the assignment copies the array, or an assignment with an array left operand copies to the array from the bytes pointed to by the right pointer operand. – Eric Postpischil Jun 10 '20 at 19:34
  • " arrays in any context except the operand of sizeof or the operand of & "decay"...." --> "Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array,..." § 6.3.2.2 3 2 out 3 ain't bad – chux - Reinstate Monica Jun 10 '20 at 20:52
  • @EricPostpischil: There are lots of subtle inconsistencies with doing (b) whereby you'd end up with things that "should be" equivalent no longer being equivalent. – R.. GitHub STOP HELPING ICE Jun 10 '20 at 22:27
4

A compound array literal can be used anywhere that an actual array variable can be used. Since you can't assign one array to another array, it's also not valid to assign a compound literal to an array.

Since you can copy arrays using memcpy(), you could write:

memcpy(arr, (int[3]){[0]=1, [1]=2, [2]=3}, sizeof(arr));

Just like the array variable, the array literal decays to a pointer to its first element.

Compound struct literals can also be used in place of an actual struct variable. But structs can be assign to each other, so it's valid to assign a compound struct literal to a struct variable.

That's the difference between the two cases.

Barmar
  • 741,623
  • 53
  • 500
  • 612