【问题标题】:Why can arrays be assigned directly?为什么可以直接赋值数组?
【发布时间】:2018-05-08 04:03:43
【问题描述】:

考虑这段代码sn-p:

void foo(int a[], int b[]){
    static_assert(sizeof(a) == sizeof(int*));
    static_assert(sizeof(b) == sizeof(int*));
    b = a;
    printf("%d", b[1]);
    assert(a == b); // This also works!
}

int a[3] = {[1] = 2}, b[1];
foo(a, b);

输出(无编译错误):

2

我不明白为什么b = a 是有效的。即使数组可能会衰减为指针,但它们不应该衰减为 const 指针吗(T * const)?

【问题讨论】:

  • ab 实际上并不是数组。
  • 您可能遇到了未定义的行为,听起来像是 b 值的泄漏。
  • 您的代码中没有数组赋值。 ab 不是数组。
  • “它们不应该衰减为 const 指针吗” 你猜到了吗?

标签: c arrays variable-assignment function-parameter


【解决方案1】:

他们不能。

无法分配数组。 foo 函数中没有数组。函数参数列表中的语法int a[] 意味着声明a 具有“指向int 的指针”类型。该行为与代码为void foo(int *a, int *b) 完全相同。 (C11 6.7.6.3/7)

将一个指针分配给另一个是有效的。结果是两个指针都指向同一个位置。


即使数组可能衰减为指针,它们不应该衰减为 const 指针 (T * const) 吗?

数组“decay”产生的指针是一个右值。 const 限定符仅对左值有意义 (C11 6.7.3/4)。 (“衰减”一词是指参数的转换,而不是参数的调整)。

【讨论】:

  • @iBug C11 6.7.6.3/7
  • 您对为什么指针不是 const 的解释特别好。
  • @iBug [language-lawyer] 不是“请提供标准报价”的简写,它是专门针对规范本身提出问题的标签。
  • 虽然数组转换为指针,但函数参数调整(不转换)为指针。对于 C 标准来说,将 int a[] 调整为 int * const a 是合理的,因为这可以更好地捕捉将 a 声明为数组的意图——您不能更改数组,所以让我们将参数也不变。所以这里的答案不是右值的结果,而是标准做出的选择,可能会受到历史的影响(因为const 是新的)。
  • @EricPostpischil IMO 只会让水变得浑浊。
【解决方案2】:

引用C11,第 §6.7.6.3 章,函数声明符(包括原型)

将参数声明为‘‘数组类型’’应调整为‘‘限定指针 type'',其中类型限定符(如果有)是在 [] 的 数组类型推导。 [...]

所以,ab 实际上是指针,而不是数组。

这里没有对任何数组类型进行赋值,因此代码没有问题。

【讨论】:

  • 那么它是不是像int* 一样编译,甚至没有按原样编写?
  • @iBug 就是这个主意,是的,
  • 令我惊讶的是,人们仍然对 18 年前语言中添加的功能感到惊讶......在计算方面,您需要很长时间才能不复习知识!
  • @MM:这并不是更令人兴奋的 C99 功能之一,所以虽然我很久以前就阅读过 C99,但在任何“C99 中的新功能”参考资料中从未真正出现过这种变化锯。
【解决方案3】:

是的,将使用[] 声明的数组参数调整为 const 限定指针是有意义的。但是,当此行为建立时,const 并不存在。

在开发 C 语言时,通过传递数组的地址来传递数组是有意义的,或者更具体地说,是第一个元素的地址。您当然不想复制整个数组来传递它。传递地址是使被调用函数知道数组的一种简单方法。 (我们在 C++ 中看到的引用类型的语义还没有被发明出来。)为了让程序员更容易做到这一点,以便他们可以编写 foo(ArrayA, ArrayB) 而不是 foo(&Array[0], &ArrayB[0]),将数组转换为指向它的指针的机制发明了第一个元素。 (根据 Dennis M. Ritchie 的 M.M.The Development of the C Language,这种参数表示法已经存在于 C 的前身语言 B 中。)

没关系,您已经隐藏了转换。但这只是调用函数的地方。在被调用的例程中,正在考虑传递数组的程序员将编写void foo(int ArrayA[], int ArrayB[])。但由于我们实际上传递的是指针,而不是数组,因此需要将它们更改为 int *ArrayAint *ArrayB。于是产生了声明为数组的参数会自动调整为指针的概念。

正如您所观察到的,这使程序员能够为参数赋值,从而改变数组的明显基地址。将声明为int ArrayA[] 的参数调整为int * const ArrayA 是有意义的,这样就无法更改参数ArrayA 的值。那么它会更像一个数组,它的地址也不能改变,所以这更符合我们在传递地址时假装传递数组的目标。

然而,当时const 并不存在,所以这是不可能的,当时没有人想过发明const(或者至少做了足够的工作以使其被语言采用) .

现在世界上有大量的源代码与非常量调整一起工作。现在更改 C 语言的规范会导致现有代码出现问题。

【讨论】:

  • 回复。您的第二段:语法foo(ArrayA, ArrayB); 已经存在于 B 语言中,其中数组实际上是指针。声明一个数组为指针和元素存储保留空间,并传递数组意味着传递指针。 C 语言改变了数组的语义(即不为指针保留空间),同时添加了衰减规则,以便现有的 B 代码可以继续工作。 Further reading
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-23
  • 1970-01-01
  • 2015-03-21
  • 1970-01-01
  • 2021-04-16
  • 1970-01-01
  • 2014-11-07
相关资源
最近更新 更多