为什么我们在C中没有数组类型的右值?

时间:2017-04-07 15:05:28

标签: c

我在一次讨论中读到了这个陈述:

  

C中没有数组类型的右值。有指针左值和右值,整数左值和右值,结构左值和右值等......但只有左值数组。当您尝试将数组类型的左值转换为右值时,您不再拥有数组,而是指向数组的第一个成员。

当我们无法更改数组的基址时,我无法得到它,那么我们如何将它用作左值,如何在赋值语句的左侧使用它的名称?

请举例说明。

4 个答案:

答案 0 :(得分:2)

C11标准草案n1570 footnote 64说:

  

名称左值最初来自赋值表达式E1 = E2,其中左操作数E1必须是(可修改的)左值。它可能更好地被视为代表 对象定位器值 。有时被称为 rvalue 的内容在此国际标准中被描述为表达式的。左值的一个明显示例是对象的标识符。作为另一个示例,如果E是一个指向对象的指针的一元表达式,*E是一个左值,用于指定E指向的对象。

因此,数组的名称是对象定位器值,因为它是一个正确的对象。但是,在值上下文中,数组的名称会衰减为指向第一个元素的指针。

如果我没记错的话,即使是 rvalues - 也就是说,类型struct的表达式的值是该语言的后来(在ANSIsh之前)的添加。这可以以向后兼容的方式完成,因为结构不会衰减成指针,但它不适用于数组。

答案 1 :(得分:1)

因为C最初是作为低级语言编写的。 AFAIK它是第一种没有输入/输出指令的语言,其原理是IO应该在C库中编码。

作为一种低级语言,它大量使用地址(读指针)。在汇编语言中,数组只是您知道第一个地址的连续内存区域。在旧的K& R C中,数组只不过是这样,并且只允许函数返回标量或指针,因为这些类型可以放在寄存器中。

那些过去的好日子里,你发现了大量悬空指针的例子,因为只要你试图返回更多的单一价值就会丢失。

然后C(ANSI C)开始表现为更民用的语言。函数获得原型以限制传递错误类型的风险,并且允许一些聚合(结构)作为参数和返回函数类型。但是由于数组通过指针广泛使用,并且因为许多现有代码实际上依赖于规则数组衰减到指向其第一个元素的指针,所以没有人想破坏它以允许处理数组相同的struct objects。

不同地说,由于结构和数组在标准的某些部分被共同设计为聚合,我们可以很容易地想象一种方法来接受两者的相同语法规则。还有其他一些语言。但它可能会破坏如此多的遗留代码,以至于它可能永远不会发生。

TL / DR:这是C从开始以来的行为方式,很多代码都依赖于这种行为。如果你不喜欢这样,你应该学习Java,这是一种更新的语言,没有更多的怪癖。但由于缺乏指针概念,可能性也较小......

答案 2 :(得分:0)

在C中,数组有左值,但事实是它不是可修改的左值。这里需要注意的另一件事是数组名称始终是左值。因为当您使用数组作为右值时,其指针指向数组的第一个元素。指针总是有rvalue。当您引用数组时,您指的是数组的所有元素,而不仅仅是第一个元素。

因此,作为结论,我说数组名称始终是一个太不可修改的左值

答案 3 :(得分:0)

  

当我们无法更改数组的基址时,我无法得到它,那么我们如何将它用作左值,如何在赋值语句的左侧使用它的名称?

你有一个核心误解。正如@AnttiHaapala已经涵盖的那样,一些表达式 expr 是一个左值并不一定意味着它是赋值运算符的可接受的左手操作数。 C有一些这样的表达式的简单例子:任何类型为const的人都是合格的。

有几种方法可以概念化为什么数组不能出现在赋值语句的左侧。请考虑以下声明:

int a[3] = { 1, 2, 3 };
int b[3] = { 4, 5, 6 };

根据那些声明和C的表达式评估规则,我们应该如何解释(无效)声明

a = b;

?为了评估赋值操作,我们必须首先评估操作数。由数组值变量的名称组成的子表达式b计算为指向该数组的第一个元素的指针(相当于&b[0])。指针不是数组,因此结果没有用于赋值给变量a的正确类型。

这本身就足够了,但我们还没有完成。要评估赋值表达式,我们必须计算子表达式a。作为数组值变量的名称,此子表达式计算为指向数组的第一个元素的指针。此外,指针不是左值,因此它不是=运算符的可接受左操作数。

并且好像这还不够,标准也说

  

可修改的左值没有数组类型的左值,   没有不完整的类型,没有const限定   类型,[...]

Paragraph 6.3.2.1/1;重点补充)。因此,即使您在赋值运算符的左侧忽略了数组到指针的衰减,分配仍然无效,因为在任何情况下数组都不是可修改的左值。

选择对您最有意义的这些解释,或者确实选择所有这些解释。该标准提供了多种方式得出相同的结论;虽然它并不完美,但它非常善于自我保持。