如果C有指针,为什么C需要数组呢?

如果我们可以使用指针和malloc来创建和使用数组,为什么数组类型存在于C中? 如果我们可以使用指针而不是没有必要吗?

数组比动态内存分配更快。

数组在“编译时”被“分配”,而malloc在运行时分配。 分配需要时间。

此外,C并未强制要求malloc()和朋友在独立实现中可用。


编辑

数组示例

 #define DECK_SIZE 52 int main(void) { int deck[DECK_SIZE]; play(deck, DECK_SIZE); return 0; } 

malloc()示例

 int main(void) { size_t len = 52; int *deck = malloc(len * sizeof *deck); if (deck) { play(deck, len); } free(deck); return 0; } 

在数组版本中,编译器在创建程序时保留了deck数组的空间(但是,当然,只有在运行程序时才保留/占用内存),在malloc()版本中,空间对于deckarrays,必须在程序的每次运行时请求。

数组永远不会改变大小,malloc内存可以在需要时增长。

如果您只需要固定数量的元素,请使用数组(在您的实现范围内)。 如果您需要在程序运行期间可以增长或缩小的内存,请使用malloc()和朋友。

这不是一个糟糕的问题。 实际上,早期的C没有数组类型。

全局和静态数组在编译时分配(非常快)。 其他数组在运行时(快速)分配在堆栈上。 使用malloc分配内存(用于数组或其他方式)要慢得多。 在解除分配中可以看到类似的事情:动态分配的内存释放速度较慢。

速度不是唯一的问题。 数组类型在超出范围时会自动释放,因此不会被错误地“泄露”。 你不必担心意外地释放两次东西,等等。 它们还使静态分析工具更容易检测错误。

您可能会认为有一个函数_alloca()可以让您从堆栈中分配内存。 是的,没有技术原因可以通过_alloca()_alloca()数组。 但是,我认为数组使用起来更方便。 此外,编译器更容易优化数组的使用,而不是具有_alloca()返回值的指针,因为很明显堆栈分配的数组与堆栈指针的偏移是什么,而如果_alloca()是处理就像一个黑盒函数调用,编译器无法提前告诉这个值。

编辑,因为tsubasa要求提供有关此分配如何发生的更多详细信息:

在x86体系结构中, ebp寄存器通常引用当前函数的堆栈帧,并用于引用堆栈分配的变量。 例如,你可能有一个位于[ebp - 8]char和一个从[ebp - 24]延伸到[ebp - 9]char数组。 也许堆栈上有更多的变量和数组。 (编译器决定如何在编译时使用堆栈帧.C99编译器允许将可变大小的数组分配给堆栈,这只是在运行时进行一些工作的问题。)

在x86代码中,指针偏移量(例如[ebp - 16] )可以在单个指令中表示。 很有效率。

现在,重要的一点是,当前上下文中的所有堆栈分配的变量和数组都是通过来自单个寄存器的偏移来检索的。 如果你调用malloc,那么(正如我所说)实际为你找到一些内存会有一些处理开销。 而且,malloc为您提供了一个新的内存地址。 假设它存储在ebx寄存器中。 您不能再使用来自ebp的偏移量,因为您无法在编译时知道该偏移量。 所以你基本上“浪费”了一个额外的寄存器,如果你使用普通的数组而不需要它。 如果你使用malloc更多的数组,你会有更多“不可预测的”指针值来放大这个问题。

数组有其用途,应尽可能使用,因为静态分配有助于使程序更稳定,并且有时需要确保不会发生内存泄漏。

存在是因为某些要求需要它们。

在诸如BASIC之类的语言中,由于语言结构的原因,您可以使用某些允许的命令,这是已知的。 那么,使用malloc创建数组,然后从字符串填充它们有什么好处?

如果我必须定义操作的名称,为什么不将它们放入数组?

C是作为通用语言编写的,这意味着它在任何情况下都应该是有用的,因此它们必须确保它具有对编写操作系统以及嵌入式系统有用的结构。

例如,数组是指定指向malloc开头的简写方式。

但是,想象一下尝试使用指针操作而不是vec[x] * vec[y]来进行矩阵数学运算。 很容易发现错误。

看到这个问题讨论空间强化和C.有时动态内存分配只是一个坏主意,我使用的C库完全没有malloc()和朋友。

您不希望卫星解除引用NULL指针,而不是希望空中交通管制软件忘记将堆块清零。

它也很重要(正如其他人所指出的那样),了解什么是C的一部分,以及将它扩展到各种统一标准(即POSIX)。

与处理指针相比,数组是一种很好的语法改进。 在处理指针时,你可能会在不知不觉中犯下各种错误。 如果因为使用了错误的字节大小而在内存中移动了太多空格怎么办?

Dennis Ritchie关于C历史的解释:

胚胎C.

NB存在如此短暂,以至于没有写出完整的描述。 它提供了int和char类型,它们的数组以及指向它们的指针,以一种典型的样式声明

 int i, j; char c, d; int iarray[10]; int ipointer[]; char carray[10]; char cpointer[]; 

数组的语义与B和BCPL完全相同:iarray和carray的声明创建单元格动态初始化,其值分别指向10个整数和字符序列中的第一个。 ipointer和cpointer的声明省略了大小,断言不应自动分配存储空间。 在过程中,语言对指针的解释与数组变量的解释相同:指针声明创建了一个与数组声明不同的单元格,只是程序员需要分配一个指示对象,而不是让编译器分配空间和初始化单元格。

存储在绑定到数组和指针名称的单元格中的值是相应存储区域的机器地址(以字节为单位)。 因此,通过指针的间接意味着没有运行时开销来将指针从字缩放到字节偏移。 另一方面,数组下标和指针算法的机器代码现在取决于数组的类型或指针:计算iarray [i]或ipointer + i暗示将加数i缩放到所引用的对象的大小。

这些语义代表了从B的简单过渡,我用它们进行了几个月的实验。 当我尝试扩展类型表示法时,问题变得明显,特别是添加结构化(记录)类型。 结构似乎应该以直观的方式映射到机器中的内存中,但是在包含数组的结构中,没有好地方存放包含数组底部的指针,也没有任何方便的方法来安排它。初始化。 例如,早期Unix系统的目录条目可能在C中描述

 struct { int inumber; char name[14]; }; 

我希望该结构不仅可以表征抽象对象,还可以描述可能从目录中读取的位集合。 编译器在哪里可以隐藏指向语义要求的名称的指针? 即使结构被认为更抽象,并且指针的空间可能以某种方式隐藏,我如何处理在分配复杂对象时正确初始化这些指针的技术问题,也许是指定包含包含任意深度结构的数组的结构?

该解决方案构成了无类型BCPL和类型C之间的演化链中的关键跳跃。它消除了存储中指针的实现,而是在表达式中提到数组名称时导致指针的创建。 在今天的C中幸存的规则是,数组类型的值在表达式中出现时转换为指向构成数组的第一个对象的指针。

用我自己的话来概括 – 如果上面的name只是一个指针,那么任何结构都会包含一个额外的指针,从而破坏它与外部对象(如目录条目)的完美映射。