为啥不允许将数组按值传递给 C 和 C++ 中的函数?

Posted

技术标签:

【中文标题】为啥不允许将数组按值传递给 C 和 C++ 中的函数?【英文标题】:Why it is not allowed to pass arrays by value to a function in C and C++?为什么不允许将数组按值传递给 C 和 C++ 中的函数? 【发布时间】:2010-10-14 20:08:05 【问题描述】:

C 和 C++ 允许将结构和对象按值传递给函数,但阻止按值传递数组。

为什么?

【问题讨论】:

【参考方案1】:

您可以按值传递数组,但您必须先将其包装在结构或类中。或者干脆使用像 std::vector 这样的类型。

我认为这个决定是为了提高效率。大多数时候都不想这样做。这与为什么没有无符号双打的原因相同。没有相关的 CPU 指令,因此您必须使效率不高的事情很难用 C++ 之类的语言来完成。

正如@litb 提到的:“C++1x 和 boost 都将原生数组包装到提供 std::array 和 boost::array 的结构中,这是我一直喜欢的,因为它允许在结构中传递和返回数组”

数组是指向包含该数组及其大小的内存的指针。请注意,它与指向数组第一个元素的指针并不完全相同。

大多数人认为您必须将数组作为指针传递并指定大小作为单独的参数,但这不是必需的。您可以传递对实际数组本身的引用,同时保持它的 sizeof() 状态。

//Here you need the size because you have reduced 
// your array to an int* pointing to the first element.
void test1(int *x, int size)

  assert(sizeof(x) == 4);


//This function can take in an array of size 10
void test2(int (&x)[10])

  assert(sizeof(x) == 40);


//Same as test2 but by pointer
void test3(int (*x)[10])

  assert(sizeof(*x) == 40);
  //Note to access elements you need to do: (*x)[i]

可能有人会说数组的大小是未知的。这不是真的。

int x[10];  
assert(sizeof(x) == 40);

但是堆上的分配呢?堆上的分配不返回数组。它们返回指向数组第一个元素的指针。所以 new 不是类型安全的。如果您确实有一个数组变量,那么您将知道它所包含的大小。

【讨论】:

你看到函数参数中的[10]了吗?这就是大小的来源。引用与它无关。 它比 test1 更安全 @Neil Butterworth:数组的大小是数组类型本身的一部分。如果您想要可变大小,最好使用 std::vector。 您说“人们认为您需要指定尺寸”。人们是对的,正如您不必要的复杂代码所证明的那样。 一切都与它有关。如果你说 void test2(int x[10]);您只会传递一个简单的指针,而那里的“10”完全被忽略且无用(甚至很危险)。现在引用接受参数的引用并避免将参数转换为指针。【参考方案2】:

编辑:我在下面留下了原始答案,但我相信大部分价值现在都在 cmets 中。我已将其设为社区 wiki,因此如果参与后续对话的任何人想要编辑答案以反映该信息,请随意。

原答案

一方面,它如何知道要分配多少堆栈?这对于结构和对象(我相信)是固定的,但是对于数组,它将取决于数组的大小,直到执行时才知道。 (即使每个调用者在编译时都知道,也可能有不同的调用者具有不同的数组大小。)您可以在参数声明中强制使用特定的数组大小,但这似乎有点奇怪。

除此之外,正如 Brian 所说,还有效率问题。

您希望通过所有这些实现什么目标?是不是想确保原始数组的内容不被改变?

【讨论】:

字符 x[10];断言(sizeof(x) == 10); 布赖恩是对的。 C++ 和 C89 中的数组在编译时总是有已知的大小:) 但我认为你的答案仍然与它有关。在很多情况下,数组会衰减为指针。并且让数组传递给函数会很危险(正是因为这种衰减)...... 在一定程度上没用,因为我们会被限制传递一个大小(即 int[42] - 但不是 int[41])。 我相信你的回答正是指出了这一点,只是从一个角度来看,它希望能够接受不同的数组大小,但在 C++/C 中不起作用,因为它需要接受数组具有不同尺寸的尺寸。所以无论如何我+1:D 数组的大小是类型本身的一部分。如果您想要可变大小,最好使用 std::vector。【参考方案3】:

在 C/C++ 中,在内部,数组作为指向某个位置的指针传递,基本上,它按值传递的。问题是,复制的值表示相同位置的内存地址。

顺便说一句,在 C++ 中,vector<T> 被复制并传递给另一个函数。

【讨论】:

最佳答案!我认为虽然它也应该强调 C/C++ 不会防止按值传递数组。 这个答案混淆了数组和指针。这听起来很简单,但我认为它确实会引起混乱。区分数组和指针的概念很重要,首先要了解它们的根本区别。 @litb:嗯,是的,知道它与从编译器的角度传递指针(例如多维数组)并不完全相同,这一点非常重要,但同样重要的是要强调实际的数组地址是复制的,不是通过引用传递的。 一个数组确实不仅仅是一个指针,否则对于 char a[1]; sizeof(a) 将等于 sizeof(char*) 而不是 1。 @Mehrdad:我知道你想说什么,但我认为你目前的解释非常接近错误。问题在于它模糊了界限——all 使用指针语义而不是值语义的情况可以解释为“嗯,它实际上是底层地址上的值语义”。【参考方案4】:

我实际上并不知道 任何 语言支持按值传递 naked 数组。这样做不会特别有用,而且会很快占用调用堆栈。

编辑:致反对者 - 如果您知道得更多,请让我们都知道。

【讨论】:

C++ 向量<...> 对象,当按值传递时,会被复制。尽管这会浪费(堆)内存,但它不会“快速粉碎调用堆栈”。 向量内的原生数组是通过引用传递的——它是一个指针。 C++ 原生数组与 C 数组完全相同。 MATLAB 甚至对于数组也具有值语义。这就是不平凡的 matlab 程序经常使用大量内存的原因之一,尽管当前版本的解释器使用写时复制来减少内存使用量。 写入时复制通过 ref 传递(R 也是如此)。阵列可能会在以后增长(取决于使用情况)这一事实不是问题。 我同意尼尔的观点。我不知道可以按值传递数组的语言。 C# 和 Java 根本不传递它们(甚至不通过引用),C++ 只能通过引用传递它们。而 C 也根本无法传递它们,只能传递一个指向它们的第一个元素的指针。【参考方案5】:

这是“仅仅因为”的答案之一。 C++ 从 C 继承了它,并且必须遵循它以保持兼容性。为了提高效率,它在 C 中是这样做的。您很少会想要在堆栈上复制一个大型数组(请记住,此处考虑 PDP-11)以将其传递给函数。

【讨论】:

【参考方案6】:

我认为数组在 C 中作为指针而不是按值传递的主要原因有 3 个。其他答案中提到了前两个:

效率 因为通常没有数组的大小信息(如果包含动态分配的数组)

但是,我认为第三个原因是:

C 从早期语言(如 B 和 BCPL)演变而来,其中数组实际上是作为指向数组数据的指针实现的

Dennis Ritchie 谈到了 C 从 BCPL 和 B 等语言的早期演变,特别是数组是如何实现的,它们是如何受到 BCPL 和 B 数组的影响的,以及它们如何以及为什么不同(同时在表达式中保持非常相似,因为数组名称衰减为表达式中的指针)。

http://plan9.bell-labs.com/cm/cs/who/dmr/chist.html

【讨论】:

以上是关于为啥不允许将数组按值传递给 C 和 C++ 中的函数?的主要内容,如果未能解决你的问题,请参考以下文章

C++:为啥对于内置(即类 C)类型,按值传递通常比按引用传递更有效

C ++:将对象按值传递给同一类的成员函数

在C ++中的继承上下文中按值传递对象[重复]

C / C ++:将带有成员数组的结构/类按值传递给函数

为啥要在 C++ 中按值传递对象 [重复]

如何将多维数组传递给 C 和 C++ 中的函数