【问题标题】:Why is assigning to an array not allowed in C? [duplicate]为什么在 C 中不允许分配给数组? [复制]
【发布时间】:2020-02-07 08:29:35
【问题描述】:

如果我写:

int some_arr[4];
some_arr = {0, 1, 2, 3};

然后我的编译器(在本例中为 GCC)会抱怨我在 { 之前没有表达式。所以我需要使用复合文字,很好:

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

现在我们看到我不能给数组赋值。

什么?

我可以用memcpy(some_arr, (int[]){0, 1, 2, 3}, sizeof(int[4])) 之类的东西“规避”这个问题,或者通过一个接一个(或通过循环)分配给some_arr 的每个元素。我无法想象GCC 无法解析我所写的单独分配(一个不关心用户的懒惰编译器甚至可能在预处理器中完成),所以它似乎归结为“标准说不”。那么为什么标准说这个特殊的东西是禁止的?

我不是在寻找标准中的语言说它是不允许的,而是在寻找关于标准的这一部分是如何形成的历史课。

【问题讨论】:

  • 有趣的是,如果数组是结构中的一个字段,那么 C 会很乐意让您分配整个结构,包括数组。 (这有时用于从函数返回堆栈分配的数组。)
  • 您要求的不是 C 的可用功能。您必须逐个分配元素。在声明中,您可以使用int some_arr[4] = {0, 1, 2, 3}; 一次性初始化数组。注意初始化和分配之间的区别。
  • C 是一种古老的语言 C 中的操作旨在模仿汇编程序 - 将数组的内容分配给数组是一项复杂的操作,需要几条指令甚至一个循环 - 所以它不是原始操作 - 因此不包括在内。
  • @KonradRudolph 我认为不同之处在于结构的大小始终是已知的,而数组的大小在传递给函数时会丢失。
  • 我猜Why can't a modifiable lvalue have an array type? 是我们最好的帖子。我试图从 Ritchie 的“C 开发”中找到任何直接引用,但没有找到。此外,this answer 的最后一次编辑几乎是 Ritchie 解释其原理的论文的准确摘要。

标签: c arrays standards


【解决方案1】:

来自 ISO/IEC 9899:1999 关于赋值运算符的约束

§6.5.16 赋值运算符应有一个可修改的左值作为其左操作数。

然后在可修改的左值上

§6.3.2.1 可修改的左值是没有数组类型的左值,确实 没有不完整的类型,没有 const 限定的类型,并且 如果是结构或联合,则没有任何成员(包括, 递归地,所有包含的聚合的任何成员或元素或 unions) 具有 const 限定类型。

为什么不呢?可能是因为数组名称最有可能衰减为指向第一个元素的指针。


但是,允许使用结构体进行数组赋值,如下所示:

//gcc 5.4.0

#include  <stdio.h>

struct A
{
    int arr[3];
    int b;
};

struct A foo()
{
    struct A a = {{1, 2, 3},10};
    return a;
}

int main(void)
{
    struct A b = foo();
    for (int i=0; i<3; i++)
          printf("%d\n",b.arr[i]);
    printf("%d\n", b.b);
}

产量

1
2
3
10

【讨论】:

  • > 可修改的左值是一个没有数组类型的左值,我讨厌听起来像一个一直在说“为什么”的蹒跚学步的孩子,但是......
  • @c-x-berger 我认为它会使编译器更复杂和更慢。
  • 很公平,我猜。对我来说,这似乎是一个奇怪的地方,但我是学习语言的人,而不是设计语言的人
  • 我认为数组衰减与这里无关。语言定义为数组类型的变量定义赋值是微不足道的,这样就不会发生衰减。不这样做是一个选择,它既不需要也不明显。
  • 基本原理只是 Ritchie 不想混淆指针赋值和数组赋值。在 B 语言中,所有数组都与指向第一个元素的指针一起分配。但是,这与 C 中的结构概念并不相符,结构应该直接对应于内存。所以 Ritchie 摆脱了 B 存储地址的方式,而是引入了数组衰减,这意味着当在表达式中使用数组时,你会得到一个指向第一个元素的指针。而不是 B 语言的第一个元素的方式是指针。都在链接的副本中。
【解决方案2】:

tl;dr

因为 Cdecided 使数组衰减为指针,并且没有为程序员提供避免它的方法。

长答案

当你写作时

int arr[4];

从那一刻起,每次在动态上下文中使用arr,C 都会认为arr&amp;arr[0],即数组衰减为指针(另请参见herehere )。

因此:

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

被认为是

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

无法分配。编译器可以使用memcpy() 实现完整的数组副本,但是C 必须提供一种方法来告诉编译器何时衰减为指针,何时不衰减。

请注意,动态上下文不同于静态上下文。 sizeof(arr)&amp;arr 是在编译时处理的静态上下文,其中 arr 被视为一个数组。

同样,初始化

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

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

是静态上下文 - 这些初始化发生在程序加载到内存时,甚至在它执行之前。

language in the standard 是:

除非它是 sizeof 运算符或一元 & 运算符的操作数,或者是用于初始化数组的字符串字面量,否则类型为 ''array of type'' 的表达式将转换为类型为 ' 的表达式'pointer to type'' 指向数组对象的初始元素,不是左值。如果数组对象有注册存储类,则行为未定义。

当数组在结构中时,例如

struct s {
    int arr[4];
};
struct s s1, s2;

那么再次使用s1.arr就像&amp;s1.arr[0]一样,不能赋值。

然而,虽然s1 = s2 是动态上下文,但并未引用数组。编译器知道它需要复制整个数组,因为它是结构定义的一部分,并且这个赋值是隐式生成的。例如,如果编译器选择使用memcpy() 来实现结构赋值,则会自动复制数组。

【讨论】:

  • 你用C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)..has type "array of type"...的引用语言回答问题并且不是左值'...'
  • 这是begging the question。该语言可以在静态上下文的定义中轻松添加可修改左值的异常,并从可修改左值中删除数组异常。语言定义的复杂性将保持不变...... C 做出的特定选择没有内在的自然原因。
  • IIUC 问题不是“为什么数组会衰减为指针”或“标准对左值数组有什么看法”,而是“为什么标准会这么说关于左值数组”,答案是“因为数组衰减为指针”。
  • “C 必须提供一种方法来告诉编译器何时衰减为指针,何时不衰减。” - 它已经做到了,见 6.3.2.1/2 。这可以很容易地修改以排除有问题的情况
  • @M.M 你别无选择。例如:int f(int a[3]) 将接受任何 int 数组或 int 指针,并且没有操作员可以更改它。您不能告诉 C 强制执行数组大小(即不衰减)。我同意它可以例外,虽然它会有点不一致,但它会非常有用。
猜你喜欢
  • 1970-01-01
  • 2011-12-20
  • 1970-01-01
  • 2015-12-02
  • 2010-11-13
  • 2018-06-20
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多