C对arrays的厌恶

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

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

#include  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:“赋值给具有数组类型的表达式”)。

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

现在,一方面,如果你在结构中“隐藏”数组,只需使用=运算符即可轻松复制大量数据(数组,如果忽略包装结构)(这甚至也很快):

 #include  #include  #include  #include  #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是非法的。

然后,函数无法返回数组。 或者如果数组用作参数,C将它们折叠成指针 – 它不关心是否明确给出了大小因此以下程序给出输出sizeof(a) = 8

 #include  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中没有真正强大的数组类型? 如果有的话会发生什么不好? 毕竟,如果一个数组隐藏在一个struct ,那么该数组的行为与Go,Rust,…一样,即数组内存中的整个数据块并传递它将复制其内容,而不仅仅是内存的地址。第一要素。 例如,像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] 

这部分问题……

这种对数组的厌恶背后有什么逻辑吗? 为什么C中没有真正强大的数组类型? 如果有的话会发生什么不好?

…这不是一个真正的代码问题,并且可以接受推测,但我认为一个简短的回答可能是有益的:当C被创建时,它的目标是具有非常少的RAM和慢CPU的机器(以千字节和兆赫为单位测量, RESP)。 它的目的是将Assembler替换为系统编程语言,但不会引入其他现有高级语言所需的开销。 出于同样的原因,C仍然是微控制器的流行语言,因为它可以控制生成的程序。

引入“强大的”数组类型会对编译器和运行时产生性能和复杂性的影响,并非所有系统都承受不起。 同时,C为程序员提供了创建自己的“健壮”数组类型的能力,并且仅在其使用合理的情况下使用它们。

在这方面我发现这篇文章很有趣: Dennis Ritchie:C语言的发展(1993)

C语言最初是在20世纪70年代早期设计的一台PDP小型计算机上 ,据报道它只占用了半个房间,尽管它的内存很大。 (那是kB,而不是MB或GB)。

完全将编译器安装到该内存中是一项真正的挑战。 因此,C语言旨在允许您编写紧凑的程序,并且添加了许多特殊的运算符(如+ =, – 和?:)以进行手动优化。

设计人员不会添加用于复制大型数组作为参数的function。 无论如何它都没有用。

在C的前身B语言中,一个数组被表示为单独分配存储的指针(参见Lars回答中的链接 )。 Ritchie想在C中避免使用这个额外的指针,因此在使用不期望数组的地方时,可以将数组名称转换为指针:

它消除了存储中指针的具体化,而是在表达式中提到数组名称时导致指针的创建。 在今天的C中幸存的规则是,数组类型的值在表达式中出现时转换为指向构成数组的第一个对象的指针。

尽管语言的语义发生了潜在的变化,但本发明使大多数现有的B代码能够继续工作。

并且struct直到后来才被添加到语言中。 您可以将结构中的数组作为参数传递,然后提供另一个选项。

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

数组是数组,指针是指针,它们不一样。
但是为了使任何可用的数组,编译器必须使用合格的指针
根据定义,数组是存储器中连续且均匀的元素序列。 到目前为止这么好,但如何与它互动?
在其他论坛上解释我已经使用过的概念,一个汇编示例:

 ;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 ,但不能反过来;-)