为什么C和C ++编译器在从不执行的时候,允许函数签名中的数组长度?

这是我在学习期间发现的:

#include<iostream> using namespace std; int dis(char a[1]) { int length = strlen(a); char c = a[2]; return length; } int main() { char b[4] = "abc"; int c = dis(b); cout << c; return 0; } 

所以在variablesint dis(char a[1])[1]似乎什么都不做,不起作用
所有,因为我可以使用a[2] 。 就像int a[]char *a 。 我知道数组的名称是一个指针,如何传达一个数组,所以我的难题不在于这个部分。

我想知道的是为什么编译器允许这种行为( int a[1] )。 还是有其他的含义,我不知道?

这是传递数组到函数的语法的一个怪癖。

实际上,在C中传递一个数组是不可能的。如果你写的语法看起来像是应该传递数组,实际上发生的事情就是传递一个指向数组的第一个元素的指针。

由于指针不包含任何长度信息,所以函数forms参数列表中[]的内容实际上被忽略。

允许这种语法的决定是在20世纪70年代做出的,自从…以来引起了许多混乱。

第一个维度的长度被忽略,但是额外维度的长度对于编译器正确计算偏移量是必要的。 在下面的例子中, foo函数传递一个指向二维数组的指针。

 #include <stdio.h> void foo(int args[10][20]) { printf("%zd\n", sizeof(args[0])); } int main(int argc, char **argv) { int a[2][20]; foo(a); return 0; } 

第一维[10]的大小被忽略; 编译器不会阻止你从最终索引(注意,正式要10个元素,但实际只提供2个)。 然而,第二维[20]的大小被用来确定每一行的步幅,在这里,forms必须与实际匹配。 同样,编译器也不会阻止你索引第二维的结尾。

从数组的底部到元素args[row][col]的字节偏移由下式决定:

 sizeof(int)*(col + 20*row) 

请注意,如果col >= 20 ,那么您将实际索引到后续行(或在整个数组的末尾)。

sizeof(args[0]) ,在sizeof(int) == 4机器上返回80 。 但是,如果我试图采取sizeof(args) ,我得到以下编译器警告:

 foo.c:5:27: warning: sizeof on array function parameter will return size of 'int (*)[20]' instead of 'int [10][20]' [-Wsizeof-array-argument] printf("%zd\n", sizeof(args)); ^ foo.c:3:14: note: declared here void foo(int args[10][20]) ^ 1 warning generated. 

在这里,编译器警告说,它只会给出数组已经衰减的指针的大小,而不是数组本身的大小。

这个问题以及如何在C ++中克服它

这个问题已经被pat和Matt广泛地解释了。 编译器基本上忽略了数组大小的第一维,实际上忽略了传递参数的大小。

另一方面,在C ++中,您可以通过两种方式轻松克服此限制:

  • 使用参考
  • 使用std::array (自C ++ 11以来)

参考

如果你的函数只是试图读取或修改现有的数组(不复制它),你可以很容易地使用引用。

例如,让我们假设你想有一个函数来重置十个int的数组,将每个元素设置为0 。 您可以通过使用以下function签名轻松完成此操作:

 void reset(int (&array)[10]) { ... } 

不仅这将工作得很好 ,但它也将强制数组的维度 。

您也可以使用模板来使上面的代码具有通用性

 template<class Type, std::size_t N> void reset(Type (&array)[N]) { ... } 

最后你可以利用const正确性。 让我们考虑一个打印10个元素的数组的函数:

 void show(const int (&array)[10]) { ... } 

通过应用const限定符,我们阻止了可能的修改 。


数组的标准库类

如果你认为上面的语法既丑陋又不必要,就像我这样做,我们可以把它扔到can中,而不是使用std::array (因为C ++ 11)。

这里是重构的代码:

 void reset(std::array<int, 10>& array) { ... } void show(std::array<int, 10> const& array) { ... } 

这不是很好吗? 更不用说,我早先教给你的通用代码技巧仍然有效:

 template<class Type, std::size_t N> void reset(std::array<Type, N>& array) { ... } template<class Type, std::size_t N> void show(const std::array<Type, N>& array) { ... } 

不仅如此,还可以免费复制和移动语义。 🙂

 void copy(std::array<Type, N> array) { // a copy of the original passed array // is made and can be dealt with indipendently // from the original } 

你还在等什么? 去使用std::array

这是C的一个有趣的function,如果你这么倾向的话,可以让你有效地将自己踢在脚下。

我认为原因在于C仅仅是汇编语言之上的一步。 大小检查类似的安全function已被删除,以达到最佳性能,如果程序员非常勤奋,这不是一件坏事。

另外,为函数参数指定一个大小的优点是,当函数被另一个程序员使用时,他们有可能会注意到大小的限制。 只是使用一个指针不会传达给下一个程序员的信息。

首先,C从不检查数组边界。 不pipe它们是本地的,全球的,静态的,参数,无论如何。 检查数组边界意味着更多的处理,而C应该是非常有效的,所以数组边界检查是由程序员在需要时完成的。

其次,有一个技巧可以将一个数组传递给一个函数。 也可以从一个函数返回一个数组。 你只需要使用struct创build一个新的数据types。 例如:

 typedef struct { int a[10]; } myarray_t; myarray_t my_function(myarray_t foo) { myarray_t bar; ... return bar; } 

你必须访问像这样的元素:foo.a [1]。 额外的“.a”可能看起来很奇怪,但这个技巧为C语言增加了很多function。

要告诉编译器myArray指向一个至less有10个整数的数组:

 void bar(int myArray[static 10]) 

如果你访问myArray [10],一个好的编译器会给你一个警告。 如果没有“静态”关键字,那么10就没有任何意义。

这是C的一个众所周知的“特性”,因为C ++应该正确地编译C代码,所以传递给C ++。

问题来自几个方面:

  1. 数组名应该完全等价于一个指针。
  2. C应该是快速的,最初开发者是一种“高级汇编程序”(特别是被devise为编写第一个“便携式操作系统”:Unix),所以它应该插入“隐藏”的代码; 运行时范围检查因此被“禁止”。
  3. 通常用于访问静态数组或dynamic数据(在堆栈中或分配)的机器代码实际上是不同的。
  4. 由于被调用的函数不能知道作为parameter passing的数组的“种类”,所有的东西都被认为是一个指针,并被视为这样。

你可以说数组在C中是不被真正支持的(这是不正确的,就像我之前说过的那样,但这是一个很好的近似)。 一个数组实际上被当作一个指向数据块的指针,并使用指针运算来访问。 由于C没有任何forms的RTTI您必须在函数原型中声明数组元素的大小(以支持指针算术)。 对于multidimensional array来说,这甚至更“真实”。

无论如何,以上都不是真的了:p

大多数现代的C / C ++编译器支持边界检查,但标准要求默认closures(为了向后兼容)。 例如,合理的最新版本的gcc使用“-O3 -Wall -Wextra”进行编译时范围检查,并使用“-fbounds-checking”检查完整的运行时间范围。

C不仅将int[5]的参数转换为*int ; 给定的声明typedef int intArray5[5]; ,它会将intArray5types的参数intArray5*int 。 在某些情况下,这种行为(虽然奇怪)是有用的(特别是像stdargs.h定义的va_list ,某些实现定义为数组)。 允许将参数定义为int[5] (忽略尺寸)而不允许直接指定int[5]是不合逻辑的。

我发现C处理数组types的参数是荒谬的,但是这是一种特殊语言的结果,其中很大一部分并没有特别明确或深思熟虑,并试图提出行为规范与现有的程序所做的一致。 从这个angular度来看,许多C的怪癖是有道理的,特别是当人们认为当他们中的许多人被发明时,我们今天所知道的大部分语言还不存在。 据我所知,在C的前身BCPL中,编译器并没有很好的跟踪variablestypes。 一个声明int arr[5]; 相当于int anonymousAllocation[5],*arr = anonymousAllocation; ; 一旦拨款被搁置。 编译器既不知道也不关心arr是指针还是数组。 当以arr[x]或者*arr访问时,无论声明如何,都将被视为指针。

还有一个问题还没有得到解答,那就是真正的问题。

已经给出的答案解释了数组无法通过值传递给C或C ++中的函数。 他们还解释说,声明为int[]的参数被视为具有int *types,并且types为int[]的variables可以被传递给这样的函数。

但是他们没有解释为什么从来没有明确提供一个数组长度的错误。

 void f(int *); // makes perfect sense void f(int []); // sort of makes sense void f(int [10]); // makes no sense 

为什么不是这些错误的最后一个?

原因是它导致typedefs的问题。

 typedef int myarray[10]; void f(myarray array); 

如果在函数参数中指定数组长度时出错,则无法在函数参数中使用myarray名称。 而且由于一些实现使用标准库types(如va_list数组types,并且所有实现都需要使jmp_buf成为一个数组types,所以如果没有标准的方法来使用这些名称来声明函数参数,那将是非常有问题的:没有这种能力,不能像vprintf这样的函数的可移植实现。