【问题标题】:Difficulty in understanding variable-length arrays in C难以理解 C 中的可变长度数组
【发布时间】:2012-04-08 11:58:20
【问题描述】:

当我发现数组大小必须在声明时给出或在运行时使用 malloc 从堆中分配时,我正在看一本书。我用 C 编写了这个程序:

#include<stdio.h>

int main() {
  int n, i;
  scanf("%d", &n);
  int a[n];
  for (i=0; i<n; i++) {
    scanf("%d", &a[i]);
  }
  for (i=0; i<n; i++) {
    printf("%d ", a[i]);
  }
  return 0;
}

这段代码运行良好。

我的问题是这段代码如何才能正常工作。数组大小必须在运行时声明或在运行时使用 malloc() 分配它是否违反了 C 的基本概念。我没有做任何这些两件事,那为什么它可以正常工作?

我的问题的解决方案是 C99 支持的可变长度数组,但如果我玩弄我的代码并将语句 int a[n];以上 scanf("%d,&n); 然后它停止工作为什么会这样。如果 C 中支持可变长度数组?

【问题讨论】:

  • 你的书,很可能,谈到了 C89;您的编译器可能使用 C99。它们是有些不同的语言(C99 是 C89 的演变;C11 是 C99 的演变)。区别之一是 VLA(可变长度数组)。
  • 您如何确定它确实工作正常?在这样的情况下,有时它似乎适用于简单的情况,但在扩展时会变得很糟糕。即使在“成功”执行中,内部存储器也很可能已损坏-您很幸运它没有破坏程序。或者正如其他人指出的那样,您可能正在使用 C99。
  • VLA 是一个 PITA(特别是如果很大的话)。如果您可以避免它们,请这样做:)

标签: c arrays declaration


【解决方案1】:

C99 标准支持variable length arrays。这些数组的长度是在运行时确定的。

【讨论】:

  • 如果这些数组的长度是在运行时确定的,那么这些数组的内存分配是如何发生的?它们是在堆上分配存储,就像我们在使用 malloc() 或在堆栈上的普通运行时数组中所做的那样?
  • @dark_shadow,这取决于实现。只需在运行时向上移动堆栈指针即可避免堆分配,这就是 alloca 函数(某些平台提供的扩展,例如 Microsoft 和 GNU)所做的。
【解决方案2】:

从 C99 开始,您可以在块范围内声明可变长度数组。

例子:

void foo(int n)
{
    int array[n];

    // Initialize the array
    for (int i = 0; i < n; i++) {
        array[i] = 42;
    }
}

【讨论】:

  • 你能解释一下block scope的含义吗?
  • @dark_shadow 你只能在函数体中声明变长数组。
  • 感谢 ouah 的帮助。请查看我修改后的问题。我在其中添加了一部分。您能告诉我原因吗?谢谢。
  • 如果将int a[n]的声明放在scanf之上,那么在声明a数组时n将不会被初始化。所以你可以得到任何n 值。
  • 谢谢明白了。我还有一个问题。这些可变长度数组是在堆栈上还是在堆上声明的内存?我认为它应该在堆栈上分配,因为它们绑定到块作用域,并且由于块作用域中的所有内容都在堆栈上,我认为这应该符合规则。我说得对吗?
【解决方案3】:

只要您在使用之前声明了数组并为其分配了内存,C 就会很高兴。 C 的“特性”之一是它不验证数组索引,因此程序员有责任确保所有内存访问都是有效的。

【讨论】:

    【解决方案4】:

    可变长度数组是 C99 中添加到 C 中的一项新功能。

    这里的“可变长度”表示数组的大小是在运行时决定的,而不是编译时决定的。这并不意味着数组的大小在创建后可以更改。数组是在声明它的地方逻辑创建的。所以你的代码看起来像。

    int n, i;
    

    创建两个变量 n 和 i。最初这些变量是未初始化的。

    scanf("%d", &n);
    

    将一个值读入 n。

    int a[n];
    

    创建一个数组“a”,其大小是 n 的当前值。

    如果您交换第二步和第三步,您将尝试创建一个大小由未初始化值确定的数组。这不太可能结束。

    C 标准并没有具体说明数组的存储方式,但实际上大多数编译器(我相信有一些例外)会在堆栈上分配它。执行此操作的正常方法是将堆栈指针复制到“帧指针”中,作为函数前导码的一部分。这允许函数动态修改堆栈指针,同时跟踪它自己的堆栈帧。

    可变长度数组是一项应谨慎使用的功能。编译器通常不会在堆栈分配上插入任何形式的溢出检查。操作系统通常会在堆栈之后插入一个“保护页面”以检测堆栈溢出并引发错误或增加堆栈,但足够大的数组可以轻松跳过保护页面。

    【讨论】:

      猜你喜欢
      • 2021-06-27
      • 2021-07-11
      • 2018-12-16
      • 1970-01-01
      • 2012-12-14
      • 2014-03-27
      • 2014-11-14
      相关资源
      最近更新 更多