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

时间:2014-12-06 22:12:45

标签: 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. 调整大小:仅使用指向已分配内存的指针调整大小。

  5. 我的问题是:

    • 有什么不同(我对实用建议感兴趣)?
    • 程序员可以使用两种长度可变的数组来解决哪些问题?
    • 何时选择VLA但是在动态数组分配时?
    • 什么是更快:VLA或malloc +免费?

2 个答案:

答案 0 :(得分:6)

一些实用的建议:

  • VLA实际上位于空间有限的堆栈上,而malloc()及其朋友在堆上分配,这可能允许更大的分配。此外,您可以更好地控制该流程,因为如果malloc()失败,NULL可能会返回__STDC_NO_VLA__。换句话说,你必须小心使用VLA,而不是用runtine来炸你的堆栈。
  • 并非所有编译器都支持VLA,例如视觉工作室。此外C11 marked them作为可选功能,并且在定义malloc()宏时不允许支持它们。

根据我的经验(数学计划,如通过试验分部查找素数,Miller-Rabin等)我不会说VLA比malloc()快。当然,malloc()呼叫有一些开销,但更重要的是数据访问效率。


这是一些快速&使用GNU / Linux x86-64和GCC编译器进行脏比较。请注意,结果可能因平台或甚至编译器的版本而异。您可以使用数据访问prime-trial-gen.c与VLA基准进行基本(虽然非常完成)。

#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

编译&amp;运行:

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; }

$ 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

编译&amp; run(malloc版本):

$ 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

编译&amp;跑(VLA版):

π(10**7)

您可能check 664,579确实是{{1}}。请注意,两个执行时间几乎相同。

答案 1 :(得分:2)

VLA的一个优点是您可以将可变尺寸的数组传递给函数,这在处理(大小合适的)矩阵时非常方便,例如:

int n = 4;
int m = 5;
int matrix[n][m];
// …code to initialize matrix…
another_func(n, m, matrix);
// No call to free()

其中:

void another_func(int n, int m, int matrix[n][m])
{
    int sum = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            // …use matrix just like normal…
            sum += matrix[i][j];
        }
    }
    // …do something with sum…
}

这是特别有价值的,因为使用malloc()而不使用VLA的替代方案意味着您必须在被调用函数中手动执行下标计算,或者您必须创建指针向量。

手动下标计算

int n = 4;
int m = 5;
int *matrix = malloc(sizeof(*matrix) * n * m);
// …code to initialize matrix…
another_func2(n, m, matrix);
free(matrix);

void another_func2(int n, int m, int *matrix)
{
    int sum = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            // …do manual subscripting…
            sum += matrix[i * m + j];
        }
    }
    // …do something with sum…
}

指针向量

int n = 4;
int m = 5;
int **matrix = malloc(sizeof(*matrix) * n);
for (int i = 0; i < n; i++)
    matrix[i] = malloc(sizeof(matrix[i] * m);
// …code to initialize matrix…
another_func2(n, m, matrix);
for (int i = 0; i < n; i++)
    free(matrix[i]);
free(matrix);

void another_func3(int n, int m, int **matrix)
{
    int sum = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            // …use matrix 'just' like normal…
            // …but there is an extra pointer indirection hidden in this notation…
            sum += matrix[i][j];
        }
    }
    // …do something with sum…
}

此表单可以优化为两个分配:

int n = 4;
int m = 5;
int **matrix = malloc(sizeof(*matrix) * n);
int *values = malloc(sizeof(*values) * n * m);
for (int i = 0; i < n; i++)
    matrix[i] = &values[i * m];
// …code to initialize matrix…
another_func2(n, m, matrix);
free(values);
free(matrix);

优势VLA

使用VLA时,要做的簿记工作较少。但是如果你需要处理大小不合适的数组,那么malloc()仍会得分。如果您需要小心,可以将VLA与malloc()等人一起使用 - 请参阅calloc() for an array of array with negative index in C以获取示例。