何时在C中使用可变长度数组,但是在动态分配时?
我在C99中找到了可变长度数组,但看起来它的行为与malloc + free几乎相同。
我找到的实际差异:
-
太大的arrays处理:
unsigned size = 4000000000; int* ptr = malloc(size); // ptr is 0, program doesn't crash int array[size]; // segmentation fault, program crashes
-
内存泄漏:只能在动态数组分配中使用:
int* ptr = malloc(size); ... if(...) return; ... free(ptr);
-
对象的生命和从函数返回的可能性:动态分配的数组生命直到内存被释放,并且可以从分配内存的函数返回。
-
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中具有负索引的数组数组 。