为什么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 ++。
问题来自几个方面:
- 数组名应该完全等价于一个指针。
- C应该是快速的,最初开发者是一种“高级汇编程序”(特别是被devise为编写第一个“便携式操作系统”:Unix),所以它不应该插入“隐藏”的代码; 运行时范围检查因此被“禁止”。
- 通常用于访问静态数组或dynamic数据(在堆栈中或分配)的机器代码实际上是不同的。
- 由于被调用的函数不能知道作为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];
,它会将intArray5
types的参数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
这样的函数的可移植实现。