C对数组的厌恶

C's aversion to arrays

在 C 的介绍性书籍中,经常声称指针或多或少 数组。这充其量不是一个巨大的简化吗?

C语言中的数组类型,它的行为与指针完全不同,例如:

#include <stdio.h>

int main(int argc, char *argv[]){
  int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  int *b = a;
  printf("sizeof(a) = %lu\n", sizeof(a));
  printf("sizeof(b) = %lu\n", sizeof(b));
  return 0;
}

给出输出

sizeof(a) = 40 
sizeof(b) = 8 

或者另一个例子 a = b 会产生编译错误(GCC:"assignment to expression with array type")。

指针和数组之间当然有密切的关系,在这个意义上,是的,数组变量的内容本身是第一个数组元素的内存地址,例如int a[10] = {777, 1, 2, 3, 4, 5, 6, 7, 8, 9}; printf("a = %ul\n", a); 打印包含 777 的地址。

现在,一方面,如果您 'hide' 结构中的数组,只需使用 = 运算符就可以轻松复制大量数据(如果忽略包装结构,则为数组) (而且速度也很快):

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARRAY_LENGTH 100000000

typedef struct {int arr[ARRAY_LENGTH];} struct_huge_array;

int main(int argc, char *argv[]){
  struct_huge_array *a = malloc(sizeof(struct_huge_array));
  struct_huge_array *b = malloc(sizeof(struct_huge_array));

  int *x = malloc(sizeof(int)*ARRAY_LENGTH);
  int *y = malloc(sizeof(int)*ARRAY_LENGTH);

  struct timeval start, end, diff;

  gettimeofday(&start, NULL);
  *a = *b;
  gettimeofday(&end, NULL);

  timersub(&end, &start, &diff);
  printf("Copying struct_huge_arrays took %d sec, %d µs\n", diff.tv_sec, diff.tv_usec); 

  gettimeofday(&start, NULL);
  memcpy(x, y, ARRAY_LENGTH*sizeof(int));
  gettimeofday(&end, NULL);

  timersub(&end, &start, &diff);
  printf("memcpy took %d sec, %d µs\n", diff.tv_sec, diff.tv_usec); 

  return 0;
}

输出:

Copying struct_huge_arrays took 0 sec, 345581 µs
memcpy took 0 sec, 345912 µs

但是您不能对数组本身执行此操作。对于数组 x, y(相同大小和相同类型),表达式 x = y 是非法的。

然后,函数不能 return 数组。或者,如果数组用作参数,C 将它们折叠 为指针——它不关心大小是否明确给出, 所以下面的程序给出输出 sizeof(a) = 8:

#include <stdio.h>

void f(int p[10]){
  printf("sizeof(a) = %d\n", sizeof(p));
}

int main(int argc, char *argv[]){
  int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

  f(a);

  return 0;
}

这种对数组的厌恶背后有什么逻辑吗?为什么 C 中没有 true 可靠的数组类型?如果有一个会发生什么坏事?毕竟,如果一个数组隐藏在 struct 中,那么数组 确实 的行为与在 Go、Rust 中一样,...,即数组 内存中的 whole 块并传递它会复制其内容,而不仅仅是第一个元素的内存地址。例如在 Go 中,下面的程序

package main

import "fmt"

func main() {
    a := [2]int{-777, 777}
    var b [2]int
    b = a
    b[0] = 666

    fmt.Println(a)
    fmt.Println(b)
}

给出输出:

[-777 777]
[666 777]

数组是数组,指针是指针,不一样
但是要使数组可用,编译器必须使用 限定指针.
根据定义,数组是内存中连续且同质的元素序列。到目前为止一切顺利,但如何与之互动?
为了解释我已经在其他论坛上使用的概念,一个汇编示例:

;int myarray[10] would be defined as
_myarray:    .resd  10
;now the pointer p (suppose 64 bit machine)
_p:          .resq  1 

这是编译器发出的代码,用于在全局内存中保留 10 int 的数组和指向 int 的指针。

现在提到数组的时候你觉得你能得到什么?当然只是地址(或者更好的是第一个元素的地址)。地址是什么?标准上说得叫限定指针,但是你现在才真正明白为什么会这样
现在看指针,当我们引用它时,编译器发出代码以获取地址 p 位置的内容,但我们甚至可以获取 p 本身,即指针变量的地址,使用 &p,但是我们不能用数组来做。使用 &myarray 将再次返回第一个元素的地址。
这意味着您可以将 myarray 地址分配给 p,但不能反过来 ;-)

C 语言最初是在 1970 年代初期设计的 PDP mini-computer,尽管它有 24 kB 的巨大内存,但据报道它只占了半个房间。 (那是 kB,不是 MB,也不是 GB)。

真正的挑战是将编译器安装到该内存中。所以C语言就是为了让你写出紧凑的程序而设计的,并且添加了很多特殊的运算符(如+=,--和?:)用于手动优化。

设计人员没有想到添加用于复制大型数组作为参数的功能。反正也没用。

在 C 的前身 B 语言中,数组表示为指向单独分配的存储空间的指针(请参阅 Lars 的回答中的 the link)。 Ritchie 想在 C 中避免这个额外的指针,因此想到在不需要数组的地方使用数组名时可以将其转换为指针:

It eliminated the materialization of the pointer in storage, and instead caused the creation of the pointer when the array name is mentioned in an expression. The rule, which survives in today's C, is that values of array type are converted, when they appear in expressions, into pointers to the first of the objects making up the array.

This invention enabled most existing B code to continue to work, despite the underlying shift in the language's semantics.

structs 直到后来才被添加到该语言中。您可以通过 an array inside a struct as a parameter 是一项提供另一种选择的功能。

更改数组的语法已经太晚了。它会破坏太多程序。已经有 100 多个用户...

这部分问题...

Is there any logic behind this aversion to arrays? Why isn't there a true robust array type in C? What bad would happen if there was one?

... 并不是一个真正的代码问题并且可以猜测,但我认为一个简短的答案可能是有益的:当创建 C 时,它针对的是内存很少且 CPU 速度慢的机器(以 Kilo 为单位) -字节和兆赫,分别)。它旨在取代汇编程序成为系统编程语言,但不会引入其他现有高级语言所需的开销。出于同样的原因,C 仍然是微控制器的流行语言,因为它可以让您控制生成的程序。

引入 'robust' 数组类型会对编译器和运行时产生底层性能和复杂性的损失,这并非所有系统都无法承受。同时,C 为程序员提供了创建自己的 'robust' 数组类型并仅在合理使用它们的情况下使用它们的能力。

我发现这篇文章在这种情况下很有趣:Dennis Ritchie: Development of the C Language (1993)