【问题标题】:How is assignments to compound literals useful?对复合文字的赋值有什么用?
【发布时间】:2021-01-23 09:21:12
【问题描述】:

我正在阅读复合文字,我发现它们是左值。所以我怀疑你可以分配它们,所以我做了一个实验,我注意到这个编译没有警告gcc -Wall -Wextra -pedantic

struct foo {
    int x;
    int y;
};

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

这让我很困惑,因为我真的看不出任何这可能有用的情况。您什么时候想要将任何东西 分配给 复合文字?

或者它是一种常见的未定义行为?因为它看起来很像修改字符串文字。使用与上述相同的参数编译时不会出现警告:

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

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

根据C standard 6.4.5.7修改字符串是未定义的行为

【问题讨论】:

  • 知道您是否曾经做过关于 C 的公开测验是一件好事。看起来更像是一种新兴属性,而不是预期的功能。
  • C 从什么时候开始习惯于仅仅因为它们无用而禁止构造?

标签: c gcc lvalue compound-literals


【解决方案1】:

嗯,它可能没有有用,但它是限制最少的

由于非数组类型的非 const 限定复合文字可修改的左值,并且可修改的左值可以分配给您可以做的所有事情到可修改的左值,然后你可以分配给复合文字。

相反的是,对于作为复合文字的左值,您不能做的事情会有一些额外案例。


我发现了一个可以工作的用例,它会赋值导致左值,但它不会:

既然你刚才说你不喜欢宏,那么我不确定这是否会说服你,但这使得编写一个扩展为类似的宏成为可能
foo(&((struct baz){0} = bar()))

这里bar返回一个struct baz作为值,foo需要一个指向struct的指针作为参数。如果没有这个特性,你就不能做这样的内联值传递。

【讨论】:

  • @EricPostpischil 这就是不测试时得到的结果,我只是编译这个得到error: lvalue required as unary ‘&’ operand
  • 我们可以将那部分完全删除,而不是剔除吗?
  • @SouravGhosh 为什么,其他人可以花时间测试:d
【解决方案2】:

复合文字的目标是什么?
目标是在不提供名称的情况下创建一个完全可用的堆栈。

“堆栈上完全可用的对象”是否需要成为左值?
是的。复合文字的典型用途之一是获取它们的地址并将其传递给某个函数。目标是,每当我有一个接受Foo* fooPtr 的函数时,我都可以将参数作为复合文字&(Foo){...} 提供。由于我正在获取地址并且需要将非常量指针传递给函数,因此复合文字必须是左值。

请注意,该函数随后可以使用简单的*fooPtr = (Foo){...}; 分配给对象。像这样的代码对于构造函数或重置对象的函数来说是非常典型的。该函数不知道fooPtr 是指向复合文字、命名变量还是堆上的内存块。对于所有这些情况,分配都是合法的。

你看,你可以分配给复合文字的事实只是复合文字是左值的副作用。只有当复合字面量是真正的左值时,复合字面量才能真正用作内联、即时的对象创建构造。

【讨论】:

    【解决方案3】:

    我找不到任何好的用途,但我确实发现了与字符串文字的一些显着差异。 chapter about compound literals 只出现了一次“未定义”一词,可以看出,它通常不适用于赋值或其他修改:

    16 请注意,如果使用迭代语句而不是显式 goto 和标记语句,则未命名对象的生命周期将仅是循环的主体,并且在下次进入时 p 将具有不确定的值,这将导致未定义的行为。

    所以“是不是未定义的行为”的答案似乎是:“不,不是。”

    此外,另一个区别是使用寿命。虽然字符串文字 always have static storage 复合文字没有:

    5 复合文字的值是由初始化列表初始化的未命名对象的值。如果复合文字出现在函数体之外,则该对象具有静态存储持续时间;否则,它具有与封闭块关联的自动存储持续时间。

    这意味着这完全没问题:

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

    但这不是:

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

    我也做了一个实验。我不是 100% 如何解释它,因为它既可以解释为静态存储,也可以解释为未定义的行为。这输出了“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);
    }
    

    但是,当我打开 -O2-O3 时,它会打印“0”,所以我很确定这是未定义的行为。取决于优化级别的不同结果是 UB 的一个非常常见的症​​状。请注意,在我打开优化之前没有发出警告。当我这样做时,我得到了这个:

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

    还有一点值得注意的是:

    7 字符串字面量和具有 const 限定类型的复合字面量不需要指定不同的对象

    这意味着两个复合文字没有 const 应该指定不同的对象,但对于字符串文字则不是这样。

    【讨论】:

      【解决方案4】:

      IMO 没有理由禁止它,即使它没有用。

      IMO 非常相似

      void foo(void)
      {
          int a = a;
      
          a = a;
      }
      

      即使a 也被编译器视为已初始化:)

      https://godbolt.org/z/Msv6q9

      【讨论】:

      • warning: variable 'a' is uninitialized when used within its own initialization [-Wuninitialized]
      • int a=a; 更危险,因为它实际上是 UB。与复合文字示例一样,尽管由于复合文字的可修改左值性(在其他上下文中有用的属性)而允许赋值,但这也是在其他上下文中有用的规则的意外结果 - 即标识符的作用域规则允许静态初始化循环链表头之类的东西以指向它们自己。
      猜你喜欢
      • 1970-01-01
      • 2014-10-25
      • 2016-04-08
      • 2019-03-10
      • 2020-10-04
      • 1970-01-01
      • 2015-10-13
      • 2021-09-10
      • 1970-01-01
      相关资源
      最近更新 更多