数组作为复合字面量

4
在C99中,我们可以使用复合字面量作为未命名数组。但这些字面量是否像100、'c'、123.4f等常量一样呢?我观察到我可以这样做:
((int []) {1,2,3})[0] = 100;

而且,我没有编译错误,并且可以猜测未命名数组的第一个元素已被修改为100。

因此,数组和复合字面量似乎是左值而不是常量值。


只是补充一下:我在标准中(至少在C99 1256草案中)没有找到“100”、“'c'”和“123.4f”是任何形式的字面量的参考。它们分别被定义为整数常量、_字符常量_(也称为_整数字符常量_,以强调int类型)和_浮点常量_。 - Grzegorz Szpetkowski
1
Shafik的回答总结了这一点; 在C99中,“字面量”表示lvalue字面量,“常量”表示rvalue字面量。这与“只读”(即const)的概念是正交的。 - M.M
你有什么问题? - David Jones
4个回答

6

这是一个左值,如果我们查看draft C99 standard6.5.2.5Compound literals,它说(我强调的):

如果类型名称指定了一个未知大小的数组,则大小由初始化列表确定,如6.7.8所规定,复合字面量的类型为完成的数组类型。否则(当类型名称指定对象类型时),复合字面量的类型为类型名称指定的类型。在任一情况下,结果都是一个左值。

如果您想要一个const版本,在同一节中它给出了以下示例:

EXAMPLE 4 A read-only compound literal can be specified through constructions like:

(const float []){1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6}
我们可以在这篇Dr Dobb的文章The New C: Compound Literals中找到术语的解释,它说:
“复合字面量不是真正的常量”,因为字面量的值可能会改变,如后面所示。这带来了一些术语。C99和C90标准[2,3]使用“常量”一词表示代表无法在语言中修改的确实不可更改的值的令牌。因此,10和3.14分别是整数十进制常量和类型为double的浮点常量。单词“字面量”用于表示可能不太常量的值的表示。例如,早期的C实现允许修改引用字符串的值。 C90和C99通过说任何修改字符串字面量的程序都具有未定义行为(这是标准的方式来说明它可能有效,或者程序可能以神秘的方式失败)来禁止该做法。 [...]

1
那么,如果字面值是常量,为什么它们被称为字面值呢? - xdevel2000
2
@xdevel2000:字面值不是常量(例如在switch语句中所需的常量);它们可能是只读值。更改只读值是未定义行为:它可能在月圆之时起作用... - pmg
lvalue并不一定意味着“可修改的lvalue”(正式的C标准术语)。只是指对象在使用它的表达式之后仍然存在。 - Lundin

1
据我所记,你是正确的,复合字面量是lvalue*,你也可以取这种字面量的指针(它指向其第一个元素):
int *p = (int []){1, 2, 3};
*p = 5; /* modified first element */

对于这样的复合字面量,也可以应用const限定符,使元素为只读:

const int *p = (const int []){1, 2, 3};
*p = 5; /* wrong, violation of `const` qualifier */

*请注意,这并不意味着它自动成为可修改的左值(因此可以用作赋值运算符的左操作数),因为它具有数组类型,并参考C99草案6.3.2.1 Lvalues,arrays,and function designators

可修改的左值是一个没有数组类型的左值,[...]


1

参考 C11 标准草案 N1570

第 6.5.2.5 段第 4 段:

无论哪种情况,结果都是一个左值。

“左值”大致上是指指定对象的表达式——但需要注意并非所有左值都是可修改的。一个简单的例子:

const int x = 42;

名称 x 是一个左值,但它不是可修改的左值。(数组类型的表达式不能是可修改的左值,因为你不能对数组对象进行赋值,但数组的元素可能是可修改的。)
同一节的第5段:

复合字面量的值是由初始化列表初始化的未命名对象的值。如果复合字面量出现在函数体外部,则该对象具有静态存储期;否则,它具有与封闭块相关联的自动存储期。

描述复合字面量的部分并没有明确说明未命名对象是否可修改。在缺乏这样的声明的情况下,该对象被认为是可修改的,除非类型被标记为 const
问题中的示例:
((int []) {1,2,3})[0] = 100;

这种语法并不是特别有用,因为在赋值之后没有办法引用未命名的对象。但是类似的结构可以非常有用。以下是一个人为制造的例子:

#include <stdio.h>
int main(void) {
    int *ptr = (int[]){1, 2, 3};
    ptr[0] = 100;
    printf("%d %d %d\n", ptr[0], ptr[1], ptr[2]);
}

如上所述,数组具有自动存储期限,这意味着如果它在函数内部创建,当函数返回时它将停止存在。复合字面值不能替代malloc

“描述复合字面量的部分并没有明确说明未命名对象是否可修改。” 从C99 6.3.2.1 可以得出结论,一个“可修改的左值”是指不具有数组类型的左值。因此,这样的复合字面量不是“可修改的左值”吗?我在我的答案中试图解释这一点,并假设这样的字面量在这方面实际上被视为数组类型。 - Grzegorz Szpetkowski
@GrzegorzSzpetkowski:数组类型的复合字面量是数组类型的lvalue。它不是可修改的lvalue,但是数组对象的元素可以被修改。 - Keith Thompson
在没有这样的声明的情况下,该对象被视为可修改,除非类型是const限定的。你是如何得出这个结论的?对我来说似乎相当不清楚。缺少文本说明非const限定的复合字面量是可修改的左值可能是标准缺陷。 - Lundin
复合字面量“提供一个由初始化列表给出值的未命名对象”。没有什么可以说明该对象不可修改。复合字面量本身是一个左值,但不是可修改的左值(因为它是数组类型),但它提供的对象没有被const限定,因此可以被修改。同样地,对于int arr[2] = { 10, 20 };,名称arr不是可修改的左值,但是定义创建的对象可以被修改。 - Keith Thompson
@KeithThompson 但是复合字面量可以是结构体或单个变量。type* ptr = &(type){...}; *ptr = ...; 看起来像是一个可能的用例。我提出这个问题的原因是这里有一个类似的讨论: https://dev59.com/ZK3la4cB1Zd3GeqPTuF6。 - Lundin

1

复合字面量是左值,其元素可修改。您可以为其分配值。甚至允许指向复合字面量的指针。


lvalue不一定意味着“可修改的lvalue”(正式的C标准术语),而只是指对象在使用它的表达式之后仍然存在。 - Lundin
据我所知,标准将lvalue称为可修改的,除非它涉及数组。 - haccks
是的,但它并没有明确提到复合字面量。相关部分应该是C17 6.3.2.1:“可修改的左值是一个没有数组类型、不具有不完整类型、没有const限定类型的左值,如果它是结构体或联合体,则没有任何成员(包括所有包含的聚合体或联合体的所有成员或元素)具有const限定类型。”说这证明了复合字面量是可读/可写的是牵强附会的。值得注意的是,复合字面量可以是一个数组。 - Lundin
我最近偶然发现了这篇老贴子,原因是因为我在寻找一个新问题的答案,而这个问题与此贴有所重叠。该新问题链接为 https://dev59.com/ZK3la4cB1Zd3GeqPTuF6 。 - Lundin
@Lundin; 我所说的可修改是指它可以用作赋值运算符的左操作数,并且可以对其应用一元++运算符。在数组的情况下,它是不可修改的。在CL的情况下,这没有任何意义。你所说的是可能的,标准关于CL的说法是它是可修改的 - haccks
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接