c - 何时在 C 中使用可变长度数组,但何时使用动态分配?

标签 c arrays malloc free c99

我在 C99 中发现了可变长度数组,但看起来它的行为几乎与 malloc + free 相同。

我发现的实际差异:

  1. 数组处理太大:

    unsigned size = 4000000000;
    int* ptr = malloc(size); // ptr is 0, program doesn't crash
    int array[size]; // segmentation fault, program crashes
    
  2. 内存泄漏:只可能发生在动态数组分配中:

    int* ptr = malloc(size);
    ...
    if(...)
        return;
    ...
    free(ptr);
    
  3. 对象的生命周期和从函数返回的可能性:动态分配的数组一直存在,直到内存被释放并且可以从分配内存的函数返回。

  4. 调整大小:只有使用指向已分配内存的指针才能调整大小。

我的问题是:

  • 还有哪些区别(我对实用建议感兴趣)?
  • 对于可变长度数组的两种方式,程序员还会遇到哪些问题?
  • 何时选择 VLA 而何时选择动态数组分配?
  • 哪个更快:VLA 还是 malloc+free?

最佳答案

一些实用的建议:

  • VLA 实际上位于空间有限的堆栈上,而 malloc() 及其 friend 在堆上分配,这可能允许更大的分配。此外,您对该进程有更多的控制权,因为 malloc() 可以在失败时返回 NULL。换句话说,您必须小心 VLA,不要在运行时耗尽您的筹码。
  • 并非所有编译器都支持 VLA,例如 Visual Studio 。此外C11 marked them作为可选功能,并允许在定义 __STDC_NO_VLA__ 宏时不支持它们。

根据我的经验(数字程序,例如使用试除法查找素数、Miller-Rabin 等),我不会说 VLA 比 malloc() 快。 malloc() 调用当然有一些开销,但似乎更重要的是数据访问效率。


这里是一些使用 GNU/Linux x86-64 和 GCC 编译器的快速比较。请注意,结果可能因平台而异,甚至因编译器版本而异。您可以将 malloc() 与 VLA 基准测试用作一些基本的(虽然 非常 是完整的)数据访问。

prime-trial-gen.c:

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

bool isprime(int n);

int main(void)
{
    FILE *fp = fopen("primes.txt", "w");
    assert(fp);

    fprintf(fp, "%d\n", 2);
    for (int i = 3; i < 10000; i += 2)
        if (isprime(i))
            fprintf(fp, "%d\n", i);
    fclose(fp);
    return 0;
}

bool isprime(int n)
{
    if (n % 2 == 0)
        return false;
    for (int i = 3; i * i <= n; i += 2)
        if (n % i == 0)
            return false;
    return true;
}

编译&运行:

$ gcc -std=c99 -pedantic -Wall -W prime-trial-gen.c
$ ./a.out

然后是第二个程序,它使用生成的“素数字典”:

prime-trial-test.c:

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

bool isprime(int n, int pre_prime[], int num_pre_primes);
int get_num_lines(FILE *fp);

int main(void)
{
    FILE *fp = fopen("primes.txt", "r");
    assert(fp);

    int num_lines = get_num_lines(fp);
    rewind(fp);

#if WANT_VLA
    int pre_prime[num_lines];
#else
    int *pre_prime = malloc(num_lines * sizeof *pre_prime);
    assert(pre_prime);
#endif

    for (int i = 0; i < num_lines; i++)
        assert(fscanf(fp, "%d", pre_prime + i));
    fclose(fp);

    /* NOTE: primes.txt holds primes <= 10 000 (10**4), thus we are safe upto 10**8 */
    int num_primes = 1; // 2
    for (int i = 3; i < 10 * 1000 * 1000; i += 2)
        if (isprime(i, pre_prime, num_lines))
            ++num_primes;
    printf("pi(10 000 000) = %d\n", num_primes);

#if !WANT_VLA
    free(pre_prime);
#endif
    return 0;
}

bool isprime(int n, int pre_prime[], int num_pre_primes)
{
    for (int i = 0; i < num_pre_primes && pre_prime[i] * pre_prime[i] <= n; ++i)
        if (n % pre_prime[i] == 0)
            return false;
    return true;
}

int get_num_lines(FILE *fp)
{
    int ch, c = 0;

    while ((ch = fgetc(fp)) != EOF)
        if (ch == '\n')
            ++c;
    return c;
}

编译运行(malloc 版本):

$ gcc -O2 -std=c99 -pedantic -Wall -W prime-trial-test.c
$ time ./a.out
pi(10 000 000) = 664579

real    0m1.930s
user    0m1.903s
sys 0m0.013s

编译运行(VLA版):

$ gcc -DWANT_VLA=1 -O2 -std=c99 -pedantic -Wall -W prime-trial-test.c
ime ./a.out 
pi(10 000 000) = 664579

real    0m1.929s
user    0m1.907s
sys 0m0.007s

如你所愿check π(10**7) 确实是 664,579。请注意,这两个执行时间几乎相同。

关于c - 何时在 C 中使用可变长度数组,但何时使用动态分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27337168/

相关文章:

c - 为什么C标准中没有 "recalloc"?

c - 使用 strchr 和 strtol 在一行中读取可变数量的 long int

c - 我应该考虑 memmove() O(n) 还是 O(1)?

php & mysql - 遍历单行的列并将值传递到数组

java - 在 Java 中增长数组的最节省内存的方法?

c - 使用多个线程分配内存时出现段错误

c - 如何保护随机大小的内存?

c - 将 arm 的汇编代码插入到 C 函数中,如何插入外部变量?

c - 输入由空格分隔的 int 并将它们传递给 int 数组

c - 二维数组中的动态内存分配