【问题标题】:Why would C have "fake arrays"? [closed]为什么C会有“假数组”? [关闭]
【发布时间】:2017-01-14 16:11:12
【问题描述】:

我正在阅读The Unix haters handbook,在第 9 章中有些东西我不太明白:

C 也没有真正的数组。它有一些看起来像数组的东西 但实际上是一个指向内存位置的指针。

除了使用指向索引内存位置的指针之外,我真的无法想象有任何方法可以将数组存储在内存中。无论如何,C 如何实现“假”数组?这种说法是否属实?

【问题讨论】:

  • 真是一堆废话。当然 c 有数组;并且大多数变量在内存中都有一个位置。只是您可以使用指针算法来访问数组元素,仅此而已。
  • 我觉得作者只是想刻薄,没想到会有人质疑。
  • 真的觉得作者有点火鸡。
  • 作为一个问题可能有点宽泛。其他语言有效地将有关数组的形状信息保存在结构中,以便语言运行时知道诸如维数、数组每个维的下限和上限以及所需的任何其他内容,以及指向数组开头的指针。存储数组的数据。
  • UNIX-HATERS 手册是一本有趣的读物。它于 1994 年出版。它描述了 POSIX 标准帮助统一 Unix 系统行为方式之前的一个时代。它抱怨的很多东西都是固定的。但许多投诉也有一定的正义性。

标签: c arrays


【解决方案1】:

我认为作者的意思是,C 数组实际上只是指针运算的薄薄的一层。下标运算符被简单地定义为a[b] == *(a + b),因此您可以轻松地使用5[a] 而不是a[5] 并执行其他可怕的操作,例如访问数组超过最后一个索引。

与此相比,“真正的数组”将是一个知道自己的大小、不允许您进行指针运算、访问最后一个索引而不会出错或使用不同的项类型访问其内容的数组。换句话说,“真正的数组”是一种严格的抽象,它不会将您束缚在单一的表示上——例如,它可以是一个链表。

PS。为了省点麻烦:我对此没有意见,我只是解释书中的引述。

【讨论】:

  • 嗯,所以数组被允许有 O(n) 项访问并且仍然被称为数组?这有点恼火。
  • @ApproachingDarknessFish 你从哪里得到O(n)
  • @ApproachingDarknessFish 无论如何,当我看到“链表”时,我读到“缓存未命中和性能恐怖”并跑得很远
  • 这个答案在任何级别上都是错误的。 C 中的数组确实知道它自己的大小。为什么它会限制你做指针运算?如果您愿意,C 不会限制您对任何指针进行指针运算;该语言的设计理念是您知道自己在做什么,并且应该被允许这样做。正如已经指出的那样,声称可以将数组实现为链表的说法是无稽之谈。如果有一种语言可以做到这一点,那肯定不会是注重性能的 C!
  • 关于你在这里得到的唯一的东西就是第一句话。是的,作者可能试图说 C 数组只不过是指针运算的薄薄的一层。但是 C 中的很多东西也是如此,所以需要更多的解释。这并不意味着数组不是语言中真正的一流实体——它们是。初学者经常混淆数组和指针,语言在很多情况下似乎将它们混淆在一起,但它们是不同的。您所说的关于下标运算符的内容与数组无关。它只是 [] 运算符的正式语法。
【解决方案2】:

C数组和指针是有区别的,从sizeof()表达式的输出可以看出。例如:

void sample1(const char * ptr)
{
   /* s1 depends on pointer size of architecture */
   size_t s1 = sizeof(ptr); 
}
size_t sample2(const char arr[])
{
   /* s2 also depends on pointer size of architecture, because arr decays to pointer */
   size_t s2 = sizeof(arr); 
   return s2;
}
void sample3(void)
{
   const char arr[3];
   /* s3 = 3 * sizeof(char) = 3 */
   size_t s2 = sizeof(arr); 
}
void sample4(void)
{
   const char arr[3];
   /* s4 = output of sample2(arr) which... depends on pointer size of architecture, because arr decays to pointer */
   size_t s4 = sample2(arr); 
}

sample2sample4 可能是人们倾向于将 C 数组与 C 指针混为一谈的原因,因为在其他语言中,您可以简单地将数组作为参数传递给函数并让它“以同样的方式”工作就像在调用者函数中所做的那样。同样,由于 C 的工作方式,您可以传递指针而不是数组,这是“有效的”,而在其他语言中,数组和指针之间有更清晰的区别,则不会。

您还可以查看 sizeof() 输出作为 C 的按值传递语义的结果(因为 C 数组衰减为指针)。

另外,一些编译器也支持这种 C 语法:

void foo(const char arr[static 2])
{
   /* arr must be **at least** 2 elements in size, cannot pass NULL */
}

【讨论】:

  • 后者的语法是标准的 C 语言,但鲜为人知,更不容易被理解。
  • @Olaf 我知道,但是在 Unix-haters' 手册 的时代和一些(奇怪的)流行的 C 编译器普遍缺乏对现代 C 的支持之间(msvc) 我觉得最好不要在这里过多地讨论这个问题......
  • 幸运的是,像 gcc 和 clang 这样的典型 Unix 编译器并不坚持使用 18 年以来过时的标准版本。所以我们应该假设一个 Unix 问题,现代 C 是可用的(并且也推动最终摆脱这些遗留物)。
  • @Olaf 很有趣。虽然gcc -std=c11 -Wall -Wextra 可以理解语法,但当我将const char buf[2]0 传递给带有const char buf[static 4] 的函数时,它似乎并没有抱怨。我错过了什么?更新:clang -std=c11 确实发出警告:/
  • @Ruslan 当然,我可以强制转换来告诉编译器我确信我在做正确的事情。
【解决方案3】:

您引用的陈述实际上是不正确的。 C 中的数组不是指针。

在 B 和 BCPL 语言(C 的祖先)中使用了将数组实现为指针的想法,但它在向 C 的过渡中没有幸存下来。在 C 的早期,与 B 和 BCPL 的“向后兼容性”被认为有点很重要,这就是为什么 C 数组非常模仿 B 和 BCPL 数组的行为(即 C 数组很容易“衰减”为指针)。然而,C 数组并不是“指向内存位置的指针”。

这本书的引用完全是假的。这种误解在 C 新手中相当普遍。但它是如何进入一本书的却是我无法想象的。

【讨论】:

  • 您错过了这本书的内容。这是一部史诗般的拖钓行为,而不是一本旨在教授有关 C 和 Unix 的事实正确知识的严肃书籍。
【解决方案4】:

作者可能的意思是,从程序员的角度来看,数组受到限制,使它们感觉像是二等公民。比如两个函数,一个可以,一个不行:

int finefunction() {
    int ret = 5;
    return ret;
}

int[] wtffunction() {
    int ret[1] = { 5 };
    return ret;
}

您可以通过将数组包装在结构中来解决这个问题,但这只是强调数组是不同的,它们与其他类型不同。

struct int1 {
    int a[1];
}

int[] finefunction2() {
    struct int1 ret = { { 5 } };
    return ret;
}

这样做的另一个影响是,您无法在运行时获取数组的大小:

int my_sizeof(int a[]) {
    int size = sizeof(a);
    return size;
}

int main() {
    int arr[5];
    // prints 20 4, not 20 20 as it would if arrays were 1st class things
    printf("%d %d\n", sizeof(arr), my_sizeof(arr)); 
}

作者所说的另一种表达方式是,在 C(和 C++)术语中,“数组”的含义与大多数其他语言不同。


那么,您的标题问题是,如何将“真正的数组”存储在内存中。好吧,没有一种单一的“真正的数组”。如果您想要 C 中的真正数组,您基本上有两种选择:

  1. 使用 calloc 分配缓冲区,并在此处存储指针和项计数

    struct intarrayref {
      size_t count;
      int *data;
    }
    

    这个结构基本上是对数组的引用,你可以很好地将它传递给函数等。你需要编写函数来对其进行操作,例如创建实际数据的副本。

  2. 使用flexible array member,并使用单个 calloc 分配整个结构

    struct intarrayobject {
        size_t count;
        int data[];
    }
    

在这种情况下,您可以一次性分配元数据 (count) 和数组数据空间,但代价是,您不能再将此结构作为值传递,因为那样会留下额外数据的背后。您必须将指向此结构的指针传递给函数等。因此,是否将其视为“真正的数组”或只是略微增强的普通 C 数组,这是一个意见问题。

【讨论】:

  • 数组 C中的“一流的东西”,只是它们在事后受到高度限制。
  • 没有从函数返回数组的语法。无法分配数组。对我来说听起来不是一流的。
  • @hyde - 许多其他语言的数组也是如此,例如帕斯卡。但是没有人抱怨 Pascal 没有一流的数组。一个简单的事实是,虽然 C 中的数组有点奇怪(主要是因为在许多情况下使用时它们会自动转换为指针),但它们并不等同于指针,引用书的作者似乎认为这一点忽略了。
  • @hyde:这属于“约束”类别。数组被定义为第一类类型,句点。
  • @LightnessRacesinOrbit 我稍微改变了措辞以避免术语歧义。
【解决方案5】:

就像整本书一样,这是一个拖钓案例,具体来说,这种拖钓类型涉及陈述几乎正确但错误的内容,以征求对其错误原因的愤怒回应。 C 肯定有实际的数组/数组类型,正如指向数组类型(和多维数组)的工作方式所证明的那样。

【讨论】:

  • 有一条很好的线,传统上(尤其是在这本书写成的时候)拖钓意味着一些旨在滑稽和好玩的东西,很像讽刺。直到很久以后,这个词才被重新用作骚扰、仇恨言论等的委婉说法。
猜你喜欢
  • 2021-07-01
  • 1970-01-01
  • 2020-07-03
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 2012-04-24
  • 2012-06-12
  • 2021-12-22
相关资源
最近更新 更多