在关于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) = %lun", sizeof(a));
printf("sizeof(b) = %lun", 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 = %uln", a);
打印包含 777 的地址。
中"隐藏"数组,你可以通过使用=
运算符轻松复制大量数据(如果你忽略包装结构,则复制数组)(这甚至也很快):
#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 µsn", 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 µsn", 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 <stdio.h>
void f(int p[10]){
printf("sizeof(a) = %dn", 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 语言最初是在 1970 年代初在一台 PDP 微型计算机上设计的,据报道,尽管它有巨大的 24 kB 内存,但它只填满了半个房间。(这是 kB,而不是 MB 或 GB)。
将编译器完全装入该内存是真正的挑战。因此,C 语言旨在允许您编写紧凑的程序,并且添加了相当多的特殊运算符(如 +=、--, 和 ?:)以进行手动优化。
设计人员没有想到添加用于复制大型数组作为参数的功能。反正也没用。
在 C 的前身 B 语言中,数组表示为指向单独分配的存储的指针(参见 Lars 答案中的链接)。Ritchie 想避免在 C 语言中使用这个额外的指针,因此想到数组名称可以在不需要数组的地方使用时变成指针:
它消除了存储中指针的具体化,而是在表达式中提到数组名称时导致指针的创建。在今天的 C 语言中仍然存在的规则是,当数组类型的值出现在表达式中时,它们被转换为指向构成数组的第一个对象的指针。
这项发明使大多数现有的B代码能够继续工作,尽管语言语义发生了潜在的变化。
直到后来,struct
才被添加到语言中。您可以将结构内的数组作为参数传递,这是一个提供另一种选择的功能。
更改数组的语法已经太晚了。它会破坏太多程序。已经有 100 多个用户...
这部分问题...
这种对数组的厌恶背后有什么逻辑吗?为什么 C 语言中没有真正健壮的数组类型?如果有的话,会有什么不好的事情发生?
。并不是一个真正的代码问题,可以猜测,但我认为一个简短的答案可能是有益的:当 C 被创建时,它针对的是 RAM 非常少和 CPU 速度较慢(以千字节和兆赫兹为单位)的机器。它旨在取代汇编程序作为系统编程语言,但不引入其他现有高级语言所需的开销。出于同样的原因,C 仍然是微控制器的流行语言,因为它可以控制生成的程序。
引入"健壮"的数组类型会对编译器和运行时造成底层性能和复杂性的损失,并非所有系统都无法承受。同时,C 语言为程序员提供了创建自己的"健壮"数组类型的能力,并且仅在合理使用数组类型的情况下使用它们。
我发现这篇文章在这种背景下很有趣:Dennis Ritchie: Development of the C Language (1993)
是数组,指针是指针,它们是不一样的。
但是要使数组的任何内容可用,编译器必须使用限定指针。
根据定义,数组是内存中连续且同类的元素序列。到目前为止一切顺利,但如何与之互动?
为了解释我已经在其他论坛上使用的概念,一个汇编示例:
;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
,但不能反过来;-)