Pet*_*ica 20 c arrays pointers
[这是一个受到其他地方最近讨论的启发的问题,我会用它来提供答案.]
我想知道数组"衰减"指针的奇怪C现象,例如当用作函数参数时.这似乎是不安全的.用它明确地传递长度也是不方便的.我可以通过其他类型的聚合 - 结构 - 完全符合价值; 结构不会腐烂.
这个设计决定背后的理由是什么?它如何与语言集成?为什么结构有区别?
Pet*_*ica 19
合理
让我们检查一下函数调用,因为那里的问题很明显:为什么数组不是简单地通过值作为数组传递给函数作为副本?
首先是一个纯粹的实用理由:阵列可能很大; 可能不建议按价值传递它们,因为它们可能超过堆栈大小,特别是在20世纪70年代.第一批编译器写在PDP-7上,内存大约为9 kB.
根植于该语言还有一个更为技术性的原因.很难为函数调用生成代码,其参数的大小在编译时是未知的.对于所有数组,包括现代C中的可变长度数组,只需将地址放在调用堆栈上.地址的大小当然是众所周知的.即使具有携带运行时大小信息的精巧数组类型的语言也不会传递堆栈上正确的对象.这些语言通常会传递"句柄",这也是C已经有效地完成了40年.见乔恩斯基特这里和说明的解释,他引用(原文如此)这里.
现在,一种语言可以要求数组始终具有完整的类型; 即无论何时使用,其包括尺寸的完整声明必须是可见的.毕竟,这就是C对结构的要求(当它们被访问时).因此,结构可以按值传递给函数.要求数组的完整类型也可以使函数调用易于编译,并且无需传递额外的长度参数:sizeof()
仍然可以在被调用者中按预期工作.但想象一下这意味着什么.如果大小实际上是数组参数类型的一部分,那么我们需要为每个数组大小设置一个不同的函数:
// for user input.
int average_ten(int arr[10]);
// for my new Hasselblad.
int average_twohundredfivemilliononehundredfourtyfivethousandsixhundred(int arr[16544*12400]);
// ...
Run Code Online (Sandbox Code Playgroud)
事实上,它与传递结构完全相当,如果它们的元素不同(例如,一个结构有10个int元素,一个结构有16544*12400),它们的类型不同.很明显,阵列需要更大的灵活性.例如,如所示,人们无法合理地提供通常可用的带有数组参数的库函数.
事实上,当一个函数引用一个数组时,这个"强类型难题"就是C++中发生的事情.这也是没有人这样做的原因,至少没有明确说明.除了针对特定用途的情况外,在通用代码中使用它是完全不方便的:C++模板提供了C语言中没有的编译时灵活性.
如果在现有的C中,确实应该通过值传递已知大小的数组,则总是有可能将它们包装在结构中.我记得Solaris上的一些IP相关头文件定义了包含数组的系列结构,允许复制它们.因为结构的字节布局是固定的并且是已知的,所以这是有意义的.
在某些背景下,阅读丹尼斯·里奇的"C语言的发展"也很有意思C.C的前身BCPL的起源没有任何数组; 内存只是同类线性内存,带有指针.
AnT*_*AnT 11
这个问题的答案可以在Dennis Ritchie的"C语言的发展"一文中找到(参见"胚胎C"部分)
根据Dennis Ritchie的说法,C的新生版本直接继承/采用了B和BCPL语言的数组语义--C的前身.在这些语言中,数组实际上是作为物理指针实现的.这些指针指向独立分配的包含实际数组元素的内存块.这些指针在运行时初始化.即回到B和BCPL天数阵列被实现为"二进制"(二分)对象:指向独立数据块的独立指针.除了自动初始化数组指针这一事实外,这些语言中的指针和数组语义没有区别.在任何时候都可以在B和BCPL中重新分配一个数组指针,使其指向其他地方.
最初,这种对数组语义的方法得到了C的继承.但是,当将struct
类型引入语言时(B和BCPL都没有),它的缺点就变得很明显了.而这个想法是结构应该自然能够包含数组.然而,继续坚持B/BCPL阵列的上述"二分"性质将立即导致结构的许多明显的并发症.例如,内部有数组的struct对象在定义时需要非平凡的"构造".复制这样的结构对象是不可能的 - 原始memcpy
调用将复制数组指针而不复制实际数据.一个人将无法malloc
构造对象,因为malloc
只能分配原始内存并且不会触发任何非平凡的初始化.等等等等.
这被认为是不可接受的,这导致了C阵列的重新设计.Ritchie没有通过物理指针实现数组,而是决定完全摆脱指针.新阵列实现为单个立即内存块,这正是我们今天在C中所拥有的.但是,出于向后兼容性的原因,B/BCPL数组的行为尽可能在表面上保留(模拟):新的C数组很容易衰减到临时指针值,指向数组的开头.其余的阵列功能保持不变,依赖于现有的衰减结果.
引用前面提到的论文
该解决方案构成了无类型BCPL和类型C之间的进化链中的关键跳跃.它消除了存储中指针的实现,而是在表达式中提到数组名称时导致指针的创建.在今天的C中存活的规则是,数组类型的值在表达式中出现时转换为指向构成数组的第一个对象的指针.
尽管语言的语义存在潜在的变化,但本发明使大多数现有的B代码能够继续工作.为数组名称分配新值以调整其原点的少数程序 - 可能在B和BCPL中,在C中无意义 - 很容易修复.更重要的是,新语言保留了对数组语义的连贯且可行(如果不寻常)的解释,同时为更全面的类型结构开辟了道路.
因此,对"为什么"问题的直接回答如下:C中的数组被设计为衰减指针,以便模拟(尽可能接近)B和BCPL语言中数组的历史行为.