阻止分配给数组的理由是什么?

What's the rationale for preventing assignment to arrays?

我已经尝试 google 这个并且已经阅读:

但它们都表明了一个显而易见的事实:您不能为数组赋值,因为标准是这样规定的。这很好,但我想知道 为什么 该标准不包括对分配给数组的支持。标准委员会详细讨论了事情,如果他们从未讨论过使数组可分配,我会感到惊讶。假设他们已经讨论过,他们一定有一些理由不让数组分配给。

我的意思是,我们可以将一个数组放入一个结构中并分配给该结构就好了:

struct wrapper
{
    int array[2];
};

struct wrapper a = {{1, 2}};
struct wrapper b = {{3, 4}};
a = b; // legal

但是直接使用数组是被禁止的,即使它有效地完成了同样的事情:

int a[2] = {1, 2};
int b[2] = {3, 4};
a = b; // Not legal

标准委员会禁止赋值给数组的理由是什么?

原因基本上是历史性的。甚至在 ISO C89 之前就有一个 C,在 Kernighan 和 Ritchie 之后被称为 "K&R" C。该语言被设计得足够小,因此编译器将适合 64kb 的严重有限(按照今天的标准)内存。

此语言不允许分配数组。如果你想复制相同大小的数组,memcpy 可以满足你的需要。写 memcpy(a, b, sizeof a) 而不是 a = b 当然不是什么大问题。它具有可推广到不同大小的数组和数组切片的额外优势。

有趣的是,您提到的 struct 分配解决方法 在 K&R C 中也不起作用 。您必须一个接一个地分配成员,或者再次使用 memcpy。 K&R 的C 编程语言第一版 提到struct 赋值是该语言未来实现的一项功能。这最终发生在 C89 上。

C 的编写方式是在计算数组表达式时计算第一个元素的地址。

引用 this answer 的摘录:

This is why you can't do something like

int a[N], b[N];
a = b;

because both a and b evaluate to pointer values in that context; it's equivalent to writing 3 = 4. There's nothing in memory that actually stores the address of the first element in the array; the compiler simply computes it during the translation phase.

答案很简单:在委员会介入之前从未被允许(甚至 struct-assignment 被认为太重),并且考虑到数组衰减,允许它会有各种 有趣后果。

让我们看看会发生什么变化:

int a[3], b[3], *c = b, *d = b;
a = b; // Currently error, would assign elements
a = c; // Currently error, might assign array of 3?
c = a; // Currently pointer assignment with array decay
c = d; // Currently pointer assignemnt

因此,允许数组赋值将使(最多)两个当前不允许的赋值有效。

但这不是问题,问题在于几乎相同的表达式会产生截然不同的结果。

如果您认为函数参数中的数组表示法目前只是指针的另一种表示法,那将变得特别刺激。
如果引入数组赋值,那将变得更加混乱。
并不是说有足够多的人没有像今天这样完全迷惑...

int g(int* x);  // Function receiving pointer to int and returning int
int f(int x[3]);// Currently the same. What afterwards? Change to value-copy?

在 C 中,赋值将固定大小对象的内容复制到另一个固定大小对象。对于标量类型(整数、浮点数、指针、自 C99 以来的复杂类型),这是定义明确且实现起来相当简单的。结构的赋值几乎一样简单;较大的可能需要调用 memcpy() 或等价物,但它仍然很简单,因为大小和对齐方式在编译时已知。

数组是另一回事。大多数数组对象的大小直到 运行 时间才确定。一个很好的例子是 argv。 运行time 环境为每个命令行参数构造一个 char 数组,以及一个包含指向参数指针的 char* 数组。这些通过 argvchar** 以及 argv 的元素指向的动态分配的 char[] 数组提供给 main

C 数组本身就是对象,但它们通常不作为对象访问。相反,它们的元素是通过指针访问的,代码使用指针算法从一个元素遍历到下一个元素。

语言可以设计为将数组视为 first-class 对象,并进行赋值——但这很复杂。作为语言设计者,你必须判断一个包含 10 个整数的数组和一个包含 20 个整数的数组是否是同一类型。如果是,您必须决定当您尝试将一个分配给另一个时会发生什么。它复制较小的尺寸吗?它会导致 运行time 异常吗?是否必须添加 slice 操作才能对数组的子集进行操作?

如果 int[10]int[20] 是没有隐式转换的不同类型,则数组操作不灵活(例如,参见 Pascal)。

所有这些东西都可以定义(参见 Ada),但只能通过定义比 C 中的典型结构更高级的构造。相反,C 的设计者(主要是 Dennis Ritchie)选择为数组提供低级操作.诚然,它有时会带来不便,但它是一个可用于实现任何其他语言的所有高级数组操作的框架。

了解 的目的不是 使数组表达式不可赋值;那不是目标1。相反,这种行为不符合 Ritchie 在编译器中简化数组处理的设计决策,但作为交换,创建了数组表达式 "second-class" 对象;他们在大多数情况下都失去了 "array-ness"。

阅读this paper (especially the section titled "Embryonic C") for some background; I also have a more detailed answer here


1。除了 Perl 或 PHP2 之外,大多数明显的语言 WTF 通常是设计事故或妥协的结果;大多数语言并不是故意设计成愚蠢的。

2. 我只是在玩一点点; Perl 和 PHP 是一团糟。

也许将问题转过来问问为什么要分配数组(或结构)而不是使用指针会有所帮助?这更清晰和更容易理解(至少如果你已经吸收了 C 的禅宗),并且它的好处是不会隐藏很多工作隐藏在 "simple" 多兆字节分配下的事实数组。