我在 C99 中发现了可变长度数组,但看起来它的行为几乎与 malloc + free 相同。
我发现的实际差异:
数组处理太大:
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);
对象的生命周期和从函数返回的可能性:动态分配的数组一直存在,直到内存被释放并且可以从分配内存的函数返回。
调整大小:只有使用指向已分配内存的指针才能调整大小。
我的问题是:
- 还有哪些区别(我对实用建议感兴趣)?
- 对于可变长度数组的两种方式,程序员还会遇到哪些问题?
- 何时选择 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/