为什么在C语言中不能将一个数组赋值给另一个数组?

8
#include<stdio.h>    

int main(){    
  int a[] = {1,2,3};
  int b[] = {4,5,6};
  b = a;
  return 0;  
 } 

导致此错误的结果:

array type 'int [3]' is not assignable

我知道数组是左值且不可赋值,但在这种情况下,编译器只需要重新分配一个指针即可。 b 应该指向 a 的地址。为什么这样做不可行?


3
因为在C语言中不是这样做的。例如可以使用循环或者memcpy - Fiddling Bits
2
你可以使用指针来操作 bint *b = a; - Serge
1
数组不是指针。在函数调用等情况下,数组的名称会衰减为指针。就是这样。至于为什么C语言不会自动复制所有元素,因为它最初并非以此设计,并且语言演变缓慢。普通数组仍然可以说是C和C++中的主要奇特之处。 - underscore_d
5个回答

9
“我知道数组是lvalue,不能被分配,但在这种情况下,编译器所要做的就是重新分配一个指针。”“b应该只是指向a的地址。为什么不能做到这一点?”您似乎在这里混淆了一些东西。“b”不是一个“指针”。它是由三个int元素组成的“数组”。
b = a;

由于在这里,b在赋值中被用作左值,因此它被视为int [3]类型,而不是int *类型。指针衰减规则对于b没有影响,只对a作为右值有效。

在C语言中,无法使用b = a;将一个数组(这里是b)通过另一个数组的指向第一个元素的指针(这里是a)进行赋值。

该语法不允许这样做。

这就是错误信息

"array type 'int [3]' is not assignable"

针对b的意思。

此外,您似乎误解了指针衰减规则,认为数组被转换为任何方式都可以存储不同对象位置的指针对象

这是不正确的。这种转换以非常隐式的方式发生,并且是本SO问题的主题:

Is the array to pointer decay changed to a pointer object?


如果您想将数组 a 中的值赋给数组 b,则可以使用 memcpy() 函数:
memcpy(b, a, sizeof(a));

7
因为b不是指针。当你声明并分配ab时,你得到的是:
+---+
| 1 | a[0]
+---+
| 2 | a[1]
+---+
| 3 | a[2]
+---+
 ...
+---+
| 4 | b[0]
+---+
| 5 | b[1]
+---+
| 6 | b[2]
+---+

没有为任何指针预留空间。数组元素本身就是指针对象,没有单独的指针对象 ab

C语言源于一种早期语言叫做B,在B中有一个单独的指向第一个元素的指针:

   +---+
b: | +-+--+
   +---+  |
    ...   |
     +----+
     |
     V
   +---+
   |   | b[0]
   +---+
   |   | b[1]
   +---+
    ...
   +---+
   |   | b[N-1]
   +---+

当Dennis Ritchie开发C语言时,他想保留B语言的数组语义(具体来说,a [i] == *(a + i)),但他不想在任何地方存储单独的指针。因此,他创建了以下规则-除非它是sizeof或一元&运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,类型为“N元素数组的T”的表达式将被转换(“降解”)为类型为“指向T的指针”的表达式,并且表达式的值将是数组的第一个元素的地址,该值不是lvalue。

这有几个实际影响,其中最相关的是数组表达式可能不是赋值的目标。在大多数情况下,数组表达式失去其“数组性”,并且不像其他类型一样对待。

编辑

实际上,这样陈述是错误的——数组表达式可能不是赋值的目标,因为数组表达式不是可修改的lvalue。降解规则没有发挥作用。但是,语句“数组不像其他类型一样处理”仍然成立。

结束编辑

结果是,您无法仅使用=运算符将一个数组的内容复制到另一个数组中。您必须使用库函数如memcpy或逐个复制每个元素。


1
坦白地说,即使您使用指向内存块的指针,也不能仅使用“=”将一个数组的内容复制到另一个数组中。您仍然需要复制实际的基础内存。 - Bartek Banachewicz

6

其他人已经解释了你的错误。我写这篇答案是为了解释实际上 编译器可以将一个数组分配给另一个数组,并且您可以通过对示例代码进行最小更改来实现相同的效果。

只需将您的数组包装在一个结构中。

#include <stdio.h>

int main(){
  struct Array3 {
    int t[3];
  };
  struct Array3 a = {{1,2,3}};
  struct Array3 b = {{4,5,6}};
  a = b;
  printf("%d %d %d", a.t[0], a.t[1], a.t[2]);
  return 0;
 }

一旦将数组封装在一个结构中,复制结构的数组成员与复制任何其他成员一样。换句话说,您正在复制一个数组。这个技巧在某些情况下非常有用,比如当您真正想通过复制将数组传递给函数时。这比使用memcopy更干净和安全,后者显然也可以工作。
因此,为什么不允许顶级数组的原因不是因为编译器不能做到这一点,而只是因为大多数程序员通常不希望这样做。
通常,他们只想将数组衰减为指针。显然,这就是您认为它应该做的,并且直接复制数组很可能被禁止,以避免这种误解。

这个例子很有用且绝对相关。然而,一个数组不能被另一个数组赋值,所以你的第二句话是不正确/错误的。你展示了如何通过另一个结构对象来赋值一个结构对象,这与赋值数组是完全不同的事情。 - RobertS supports Monica Cellio
@RobertSsupportsMonicaCellio:如果允许复制数组,那么就像复制任何其他类型的对象一样:从某个地址复制一些内存块到另一个地址。很长一段时间里,我给出的结构体赋值示例也是被禁止的(是由C89引入的吗?)。虽然在复制该结构体时,它会为所有实际目的复制包括封装数组在内的所有成员。当然,这是一种底层的memcopy,但对于数组来说完全是可能的。除了这会令人困惑。 - kriss
最令人困惑的可能是参数传递,因为即使您提供了一个大小,这种情况下数组总是会被降解为指针。要修复它以允许通过赋值复制数组而不破坏大量代码将是困难的。 - kriss
我想说的只是在C语言中不可能对数组进行赋值(x=y),并暗示“实际上你可以将一个数组分配给另一个”的措辞如果严格理解是不正确的。我指的是措辞,而不是您的回答。如果您编辑掉这部分,我会给您点赞。 - RobertS supports Monica Cellio
1
@RobertS支持MonicaCellio:我稍微改变了工作方式。 - kriss

2

来自《C程序设计语言》

数组名是零号元素的地址。

数组名和指针之间有一个必须记住的区别。指针是变量,但是数组名不是变量。

我的理解是数组名是常量,因此不能被赋值。


1
在你的代码中,变量b被分配在栈上作为3个连续的int。您可以获取b的地址并将其存储在类型为int*的变量中。如果您在堆上分配数组并仅在堆栈上存储指向它的指针,则可以为其分配一个值,在这种情况下,您实际上可以更改指针的值使其与a相同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接