数组类型 - 分配/作为函数参数使用的规则

6
当我需要将一个数组传递给一个函数时,似乎以下所有的函数声明都可以使用。
void f(int arr[])  
void f(int arr[4]) // is this one correct?

针对此事:

int a[]={1,2,3,4};
f(a);

但是当我将一个数组分配给另一个数组时,它失败了。
int a[]={1,2,3,4};
int b[4] = a; // error: array must be initialized with a brace-enclosed initializer

当把一个数组作为函数参数传递时,这是正确的,但将其用于简单赋值语句的右侧是错误的,为什么呢?

3
问题的标题需要改写,当前的标题过于笼统且小写。 - Mark Rogers
7个回答

12
为了理解区别,我们需要了解两个不同的上下文
  • 上下文中,类型为T的数组的名称等同于指向类型T的指针,并且等于指向数组第一个元素的指针。
  • 对象上下文中,类型为T的数组的名称不会简化为指针。

什么是对象上下文?

a = b;中,a处于对象上下文中。当您获取变量的地址时,它在对象上下文中使用。最后,当您在变量上使用sizeof运算符时,它在对象上下文中使用。在所有其他情况下,变量都在值上下文中使用。

现在我们有了这些知识,当我们执行以下操作时:

void f(int arr[4]);

它与原文完全等价

void f(int *arr);

正如您发现的那样,我们可以在函数声明中省略大小(上面的4)。这意味着您无法知道传递给f()的“数组”的大小。稍后,当您执行以下操作时:

int a[]={1,2,3,4};
f(a);

在函数调用中,名称a处于值的上下文中,因此它被缩减为指向int的指针。这很好,因为f需要一个指向int的指针,所以函数定义和使用匹配。传递给f()的是a的第一个元素的指针(&a[0])。
在这种情况下
int a[]={1,2,3,4};
int b[4] = a;

名称 b 在对象上下文中使用,不缩减为指针。(顺便提一下,这里的a在值上下文中,并缩减为指针。)

现在,int b[4]; 分配了存储空间,值得4个整数,并将名称 b 赋给它。 a 也被分配了类似的存储空间。 因此,在实际上,上述赋值意味着“我要使存储位置与先前位置相同”。 这没有意义。

如果您想要将 a 的内容 复制b 中,则可以执行以下操作:

#include <string.h>
int b[4];
memcpy(b, a, sizeof b);

或者,如果您想要一个指向a的指针b:

int *b = a;

在这里,a处于值上下文中,并缩减为指向int的指针,因此我们可以将a分配给int *
最后,在初始化数组时,您可以为其分配明确的值:
int a[] = {1, 2, 3, 4};

这里,a有4个元素,初始化为1、2、3和4。你也可以这样做:

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

如果列表中的元素数量少于数组中的元素数量,则其余值被视为0:
int a[4] = {1, 2};

a[2]a[3]设置为0。


你能提供一下你阅读过有关对象和值上下文的参考资料吗?我很想读更多相关内容。 - John Humphreys
我从未听说过“对象上下文”和“值上下文”。 - Lightness Races in Orbit
@w00te,http://www.torek.net/torek/c/expr.html#therule 是我第一次了解对象和值上下文的地方。 - Alok Singhal
“对象上下文”基本上是指“期望一个右值的表达式”,而“值上下文”则是“期望一个左值的表达式”,在正式讲解时这样描述。在 C 中,所有函数调用都将其参数作为 rvalue(即“按值传递”),并返回 rvalue,不同运算符的操作对象也有所不同。例如,op= 需要 lvalue LHS 和 rvalue RHS,1-arg op* 以 rvalue 作为参数并返回 lvalue,2-arg op+ 和 op- 以及返回 rvalue,等等。 - Kos

7
void f(int arr[]);
void f(int arr[4]);

语法有误导性。它们与此相同:
void f(int *arr);

即,您正在传递指向数组开头的指针。您没有复制该数组。

3
是的,然而(在 C99 中),void f(int arr[static 4]) { ... } 是特殊的,因为它允许编译器假设 arr 是非空的,并且大小至少为 4。 - Jed

6

C语言不支持数组的赋值。在函数调用时,数组会衰变成指针。C语言支持指针的赋值。这个问题每天都有人问 - 你们读的是哪本没有解释这个问题的C语言教材?


3
尝试使用memcpy。
int a[]={1,2,3,4};
int b[4];
memcpy(b, a, sizeof(b));

感谢您指出这一点,Steve。我已经有一段时间没有使用C语言了。

1
memcpy(b, a, 4*sizeof(int)sizeof(b) - Steve Jessop

1
为了理解它,你必须了解机器级别上正在发生的事情。
初始化语义(= {1,2,3,4})意味着“将其放在二进制图像上,确切地这样编译”。
数组赋值将是不同的:编译器必须将其转换为循环,实际上会迭代元素。 C编译器(或C ++)从来不会这样做。 它正确地期望您自己完成。 为什么? 因为你可以。 因此,它应该是一个子例程,用C编写(memcpy)。 这一切都关乎简单和接近您的武器,即C和C ++。

0

我想澄清一下。答案中有一些误导性的提示... 所有以下函数都可以接受整数数组:

void f(int arr[])
void f(int arr[4])
void f(int *arr) 

但是形式参数并不相同。因此编译器可以以不同的方式处理它们。就内部内存管理而言,所有参数都指向指针。

void f(int arr[])

... f() 接受任意大小的数组。

void f(int arr[4])

... 形式参数表示数组大小。

void f(int *arr)

...你也可以传递一个整型指针。函数 f() 不知道有关大小的任何信息。


0
请注意,在 int a[4] 中,a 的类型是 int [4]
但是,TypeOf(&a) == int (*)[4] != int [4]
此外,请注意 a的类型是 int *,与上述所有内容都不同!
以下是一个您可以尝试的示例程序:
int main() {
  // All of these are different, incompatible types!      
  printf("%d\n", sizeof (int[4]));  // 16
  // These two may be the same size, but are *not* interchangeable!
  printf("%d\n", sizeof (int (*)[4]));  // 4
  printf("%d\n", sizeof (int *));  // 4
}

在C99中,应使用“%zu”来打印size_t。 - Jens

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