6

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

当然,指针和数组之间有密切的关系,从某种意义上说,是的,数组变量本身的内容就是第一个数组元素的内存地址,例如int a[10] = {777, 1, 2, 3, 4, 5, 6, 7, 8, 9}; printf("a = %ul\n", 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 µ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 <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 中没有真正健壮的数组类型?如果有一个会发生什么坏事?毕竟,如果一个数组隐藏在 a中,则struct该数组的行为与 Go、Rust 等中的一样,即数组内存中的整个块,传递它会复制它的内容,而不仅仅是第一个的内存地址元素。例如像在下面的程序中

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]
4

3 回答 3

6

C 语言最初是在 1970 年代初期在一台 PDP 小型计算机上设计的,据报道它只占了半个房间,尽管它有 24 kB 的巨大内存。(这是 kB,不是 MB 或 GB)。

将编译器完全安装到该内存中是真正的挑战。因此,C 语言被设计为允许您编写紧凑的程序,并且添加了很多特殊的运算符(如 +=、- 和 ?:)用于手动优化。

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

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

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

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

structs 直到后来才被添加到语言中。您可以在结构内传递一个数组作为参数,这是一个提供另一种选择的功能。

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

于 2016-02-24T09:53:32.853 回答
4

这部分问题...

这种对数组的厌恶背后有什么逻辑吗?为什么 C 中没有真正健壮的数组类型?如果有一个会发生什么坏事?

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

引入一个“健壮”的数组类型会对编译器和运行时产生底层性能和复杂性损失,这并不是所有系统都无法承受的。同时,C 为程序员提供了创建他们自己的“健壮”数组类型的能力,并且只在有理由使用它的情况下使用它们。

在这种情况下,我发现这篇文章很有趣:Dennis Ritchie:C 语言的开发(1993 年)

于 2016-02-24T09:57:59.903 回答
1

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

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

于 2016-02-24T09:02:33.857 回答