How is assignments to compound literals useful?
Asked Answered
T

4

10

I was reading about compound literals, and I saw that they are l-values. So I suspected that you can assign them, so I did an experiment and I noticed that this compiles without warnings with gcc -Wall -Wextra -pedantic:

struct foo {
    int x;
    int y;
};

int main(void) {
    (struct foo){0,0} = (struct foo){1,1};
}

It got me confused, because I really cannot see any situation at all where this could be useful. When would you ever want to assign anything to a compound literal?

Or is it one of the usual undefined behaviors? Because it looks very much alike modifying string literals. This compiles without warnings with the same parameters as above:

struct foo *ptr = &(struct foo){0,0};
*ptr = (struct foo){1, 1};

char *str = "Hello";
*str = 'X'; // Undefined behavior

Modifying the string is undefined behavior according to C standard 6.4.5.7

Taillight answered 8/10, 2020 at 10:29 Comment(4)
It's a good thing to know if you are ever doing a pub quiz about C. Seems more like an emergent property than an intended feature.Tiebout
Since when has C ever been in the habit of banning constructs merely because they are useless?Vigue
(T0){0}=(T1){0} can be used to "statically assert" that items of type T1 can be assigned to items of type T0. Sometimes stuff like this is useful in some clever macro magic. The fewer-special-cases-in-the-language point is also good.Stultz
@PSkocik Nice one. Write an answer.Taillight
P
6

Well, it might not be useful, but it is the one that has least restrictions.

Since a non-const-qualified compound literal of non-array type is a modifiable lvalue, and modifiable lvalues can be assigned to among all things that you can do to modifiable lvalues, then you can assign to compound literals.

The opposite would be that you would have some extra cases for what you cannot do to lvalues that are compound literals.


I found out a use case for this that would work would assignment result in lvalue, which it doesn't:

Since you just a moment ago stated that yo do not like macros, then I am not sure if this is going to convince you, but this makes it possible to write a macro that expands to something like
foo(&((struct baz){0} = bar()))

here bar returns a struct baz as a value, and foo needs a pointer to such struct as an argument. Without this feature you cannot do such value passing inline.

Peignoir answered 8/10, 2020 at 10:40 Comment(3)
@EricPostpischil that's what one gets when not testing, I was just compiling this to get error: lvalue required as unary ‘&’ operandZen
Instead of striking out, can we have that part removed altogether?Falster
@SouravGhosh why, someone else could spend time testing that :dZen
C
2

What is the goal of compound literals?
The goal is to create a fully usable on the stack without providing a name for it.

Does "fully usable object on the stack" entail being an lvalue?
Yes. One of the typical uses for compound literals is to have their address taken and being passed on to some function. The goal is, that, whenever I have a function that takes a Foo* fooPtr, I can supply the argument as a compound literal &(Foo){...}. Since I'm taking the address and need to pass the non-const pointer on to the function, the compound literal must be an lvalue.

Note that the function can then assign to the object with a simple *fooPtr = (Foo){...};. Code like this is very typical for constructors, or functions that reset an object. The function does not know whether fooPtr points to a compound literal, a named variable, or a memory chunk on the heap. The assignment is legal for all these cases.

You see, the fact that you can assign to a compound literal is simply a side effect of compound literals being lvalues. And compound literals are only really useful as an inline, on-the-fly object creation construct if they are really lvalues.

Camm answered 8/10, 2020 at 13:30 Comment(0)
T
1

I cannot find any good use, but I did find some notable differences to string literals. The chapter about compound literals only have one occurrence of the word "undefined" and as one can see, it's not applicable to assignments or other modifications in general:

16 Note that if an iteration statement were used instead of an explicit goto and a labeled statement, the lifetime of the unnamed object would be the body of the loop only, and on entry next time around p would have an indeterminate value, which would result in undefined behavior.

So the answer to "is it undefined behavior" seems to be: "No, it's not."

Furthermore, another difference is the lifetime. While string literals always have static storage compound literals do not:

5 The value of the compound literal is that of an unnamed object initialized by the initializer list. If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.

That would mean this is completely ok:

char *get_string() {
    return "Foobar"; // Ok! Static storage
}

But this is not:

struct my_struct {
    int x;
    int y;
};

struct my_struct *get_my_struct() {
    return &(struct my_struct){0,0}; // Not ok! Automatic storage
}

I also made an experiment. I'm not 100% how to interpret it, because it could both be interpreted as static storage and undefined behavior. This outputted "42":

int main(void) {
    struct my_struct *p = get_my_struct();
    struct my_struct *t = get_my_struct();
    p->x = 42;
    printf("%d\n", t->x);
}

However, when I turned on -O2 or -O3 it printed "0" instead, so I'm pretty sure it's undefined behavior. Different results depending on optimization level is a very common symptom of UB. Note though that no warning was issued before I turned on optimization. When I did, I got this:

warning: ‘<Uf3f0>.x’ is used uninitialized in this function [-Wuninitialized]
   16 |     printf("%d\n", t->x);
      |     ^~~~~~~~~~~~~~~~~~~~

Another thing worth noting is this:

7 String literals, and compound literals with const-qualified types, need not designate distinct objects

Which would mean that two compound literals without const should designate distinct objects, but this is not true for string literals.

Taillight answered 8/10, 2020 at 11:11 Comment(0)
T
-2

IMO there is no reason of banning it even if it is not useful.

IMO very similar to the

void foo(void)
{
    int a = a;

    a = a;
}

Even a is considered as initialized by the compiler :)

https://godbolt.org/z/Msv6q9

Transpontine answered 8/10, 2020 at 11:1 Comment(2)
warning: variable 'a' is uninitialized when used within its own initialization [-Wuninitialized]Ironhanded
int a=a; is more dangerous as that one's actually UB. Like with the compound literal example though where the assignment was allowed because of the modifiable-lvalue-ness of the compound literal (a property useful in other contexts), this too is an unintended consequence of a rule useful in other contexts--namely that the scoping rules of identifiers allow for things like circular linked-list heads to be statically initialized to point to themselves.Stultz

© 2022 - 2024 — McMap. All rights reserved.