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

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

我找到的实际差异:

  1. 太大的arrays处理:

    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. resize:仅使用指向已分配内存的指针resize。

我的问题是:

  • 有什么更多的差异(我对实用建议感兴趣)?
  • 程序员可以使用两种具有可变长度的数组的方式有什么问题?
  • 何时选择VLA但是在动态arrays分配时?
  • 什么是更快:VLA或malloc +免费?

一些实用的建议:

  • VLA实际上位于空间有限的堆栈上,而malloc()及其朋友在堆上分配,这可能允许更大的分配。 此外,您可以更好地控制该进程,因为如果malloc()失败,它可以返回NULL 。 换句话说,你必须要小心VLA不要把你的堆栈炸成runtine。
  • 并非所有编译器都支持VLA,例如Visual Studio。 此外, C11将它们标记为可选function,并且在定义__STDC_NO_VLA__宏时__STDC_NO_VLA__支持它们。

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


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

prime-trial-gen.c

 #include  #include  #include  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  #include  #include  #include  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 

你可能会检查 π(10**7)确实是664,579 。 请注意,两个执行时间几乎相同。

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()仍然会得分。 如果你小心的话,你可以使用malloc()等的VLAs - 例如,参见calloc()以获得C中具有负索引的数组数组 。