【问题标题】:What's the rationale for preventing assignment to arrays?防止分配给数组的理由是什么?
【发布时间】:2015-03-09 00:09:22
【问题描述】:

我尝试用谷歌搜索并阅读:

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

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

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

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

【问题讨论】:

  • 我认为stackoverflow.com/questions/14826952/… 中的答案会给你足够的理由
  • 要明确:您不是在寻找任何旧的对分配数组的反对意见,您希望特别标准委员会考虑和拒绝数组分配的文档。对吗?
  • @DanielA.White 所以是因为指针衰减?
  • @人们认为这主要是基于意见:它不是;我很好奇标准委员会的历史讨论(假设他们有),以及这些讨论如何得出不支持这一点的结论。
  • 一些早期的 Cdid 允许在特定情况下进行数组分配。 stackoverflow.com/questions/18412094/…`

标签: c arrays variable-assignment


【解决方案1】:

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

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

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

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

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

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

【讨论】:

    【解决方案2】:

    原因基本上是历史性的。甚至在 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 上。

    【讨论】:

    • 投反对票的人能否提供一些指导,我的回答有哪些可以改进的地方?
    【解决方案3】:

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

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

    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?
    

    【讨论】:

    • 对数组的赋值目前从来没有意义,那么扩展语言以允许对数组进行赋值(也许从相同大小的数组)会有什么问题呢?实际上,我看不出有任何混淆的余地!现有代码的含义不会改变。有没有我失踪的角落案例?无论如何,我们允许i=3,但不允许3=i,所以我们已经可以在分配周围缺乏“对称性”了。
    • @AaronMcDaid:问题是语义完全不一致。你也读过关于函数参数的那行吗? (添加示例声明)
    • 啊,是的。我曾想过数组函数参数,但只是想“他们疯了”。我忘记了充分考虑它们“看起来”像分配给数组的事实。我想从函数参数类型的顶层去掉数组——根本不应该允许它们。事实上,我认为无论如何都应该尽快完成,无论是否有任何允许分配给数组的提议。
    【解决方案4】:

    理解意图不是使数组表达式不可赋值;这不是目标1。相反,这种行为不属于 Ritchie 在编译器中简化数组处理的设计决策,但作为交换使数组表达式成为“二等”对象;在大多数情况下,它们会失去“数组性”。

    阅读this paper(尤其是标题为“胚胎C”的部分)了解一些背景;我也有更详细的答案here


    1。除了 Perl 或 PHP2 可能的例外,大多数明目张胆的语言 WTF 通常是设计的意外或妥协的结果;大多数语言并非故意设计成愚蠢的。

    2.我只是拖钓一点点; Perl 和 PHP 是一团糟。

    【讨论】:

      【解决方案5】:

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

      引用this answer的摘录:

      就是为什么你不能做类似的事情

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

      因为ab 在该上下文中都计算为指针;相当于写3 = 4。内存中实际上没有存储数组中第一个元素的地址;编译器只是在翻译阶段计算它。

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 很少,如果有的话,数组是数兆字节的,因为这不适合大多数系统的堆栈。我不是在谈论动态分配或调整大小的数组。我说的是普通数组(形式为type name[constant-integer])。这个任务也不是很多工作:它实际上只是一个memcpy,就像C中的其他任务一样。
        猜你喜欢
        • 1970-01-01
        • 2015-12-19
        • 1970-01-01
        • 2022-06-27
        • 2010-11-12
        • 1970-01-01
        • 1970-01-01
        • 2011-09-02
        相关资源
        最近更新 更多