TL;DR
如果您不知道自己在做什么,请在所有情况下使用malloc 或固定大小的数组。 VLA:s 根本不需要。请注意,VLA:s 不能是静态的,也不能是全局的。
我真的需要在这里使用malloc吗?
是的。你正在读一个文件。它们通常比适合 VLA 的要大得多。它们应该只用于小型阵列。如果有的话。
加长版
我声明这个数组的方式有什么问题吗?
这取决于。 VLA:s 作为强制组件从 C11 中删除,所以严格来说,您正在使用编译器扩展,从而降低了可移植性。将来,VLA:s 可能(机会可能极低)从您的编译器中删除。也许您还想在不支持 VLA:s 的编译器上重新编译代码。关于此的风险分析取决于您。但我可能会提到alloca 也是如此。虽然普遍可用,但标准没有要求。
另一个问题是分配是否失败。如果您使用 malloc,您有机会从中恢复,但如果您只打算这样做:
unsigned char *fileData = malloc(itemsToRead);
if(!fileData)
exit(EXIT_FAILURE);
也就是说,只是在失败时退出而不试图恢复,那么这并不重要。至少从纯粹的恢复角度来看不是。
而且,尽管 C 标准没有强制要求 VLA:s 最终位于堆栈或堆上,但据我所知,将它们放在堆栈上是很常见的。这意味着由于可用内存不足而导致分配失败的风险要高得多。在 Linux 上,堆栈通常为 8MB,在 Windows 上为 1MB。在几乎所有情况下,可用堆都高得多。声明 char arr[n] 与 char *arr = alloca(n) 基本相同,只是 sizeof 运算符的工作方式不同。
虽然我可以理解您有时可能想在 VLA 上使用 sizeof 运算符,但我发现很难找到它的真正需求。毕竟,大小永远不会改变,并且在您进行分配时大小是已知的。所以而不是:
int arr[n];
...
for(int i=0; i<sizeof(arr), ...
只要做:
const size_t size = n;
int arr[size];
...
for(int i=0; i<size; ...
VLA:s 不能替代 malloc。它们是alloca 的替代品。如果您不想将 malloc 更改为 alloca,那么您也不应该更改为 VLA。
此外,在 VLA 似乎是个好主意的许多情况下,检查大小是否低于某个限制也是一个好主意,如下所示:
int foo(size_t n)
{
if(n > LIMIT) { /* Handle error */ }
int arr[n];
/* Code */
}
这可行,但将其与此进行比较:
int foo(size_t n)
{
int *arr = malloc(n*sizeof(*arr));
if(!arr) { /* Handle error */ }
/* Code */
free(arr);
}
你并没有真正让事情变得那么容易。它仍然是一个错误检查,所以你真正摆脱的唯一一件事就是free 调用。我还可以补充一点,由于大小太大,VLA 分配失败的风险要高得多。因此,如果您知道大小很小,则无需进行检查,但话又说回来,如果您知道它很小,只需使用适合您需要的常规数组即可。
但是,我不会否认 VLA:s 有一些优点。您可以阅读有关它们的信息here. 但是 IMO,虽然它们具有这些优势,但它们并不值得。每当您发现 VLA:s 有用时,我会说您至少应该考虑切换到另一种语言。
此外,VLA:s(以及alloca)的一个优点是它们通常比malloc 更快。因此,如果您遇到性能问题,您可能希望切换到 alloca 而不是 malloc。 malloc 调用涉及向操作系统(或类似的东西)请求一块内存。操作系统然后搜索它并在找到它时返回一个指针。另一方面,alloca 调用通常只是通过在一条 cpu 指令中更改堆栈指针来实现。
有很多事情需要考虑,但我会避免使用 VLA:s。如果你问我,它们最大的风险是,由于它们很容易使用,人们对它们变得粗心。对于我认为合适的少数情况,我会改用alloca,因为这样我就不会隐藏危险。
简短总结
-
C11 及更高版本不需要 VLA:s,因此严格来说,您依赖于编译器扩展。但是,alloca 也是如此。因此,如果这是一个非常大的问题,如果您不想使用malloc,请使用固定数组。
-
VLA:s 是 alloca 而不是 malloc 的语法糖(不是 100% 正确,尤其是在处理多维数组时)。所以不要用它们代替malloc。除了 sizeof 在 VLA 上的工作方式之外,它们完全没有任何好处,只是声明更简单一些。
-
VLA:s(通常)存储在堆栈中,而 malloc 完成的分配(通常)存储在堆中,因此大分配失败的风险要高得多。
-
您无法检查 VLA 分配是否失败,因此最好提前检查大小是否太大。但是我们会进行错误检查,就像检查 malloc 是否返回 NULL 一样。
-
VLA 不能是全局的,也不能是静态的。单独的静态部分可能不会造成任何问题,但如果您想要一个全局数组,那么您将不得不使用malloc 或固定大小的数组。
这个功能很好用。
不,它没有。它具有未定义的行为。正如 Jonathan Leffler 在 cmets 中指出的那样,数组 fileName 太短了。它至少需要 12 个字节才能包含\0-终止符。您可以更改为:
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s",
"LOG", day, date, month, ".bin");
在这种情况下,数组太小的问题会通过创建扩展名为 .bi 而不是 .bin 的文件来表现出来,这比当前情况下的未定义行为更好。
您的代码中也没有错误检查。我会像这样重写它。对于那些认为 goto 不好的人来说,通常是这样,但是错误处理在经验丰富的 C 编码人员中既实用又普遍接受。另一个常见的用途是打破嵌套循环,但这不适用于这里。
int memory_get_log(unsigned char day, unsigned char date, unsigned char month){
char fileName[12];
unsigned long readItems, itemsToRead;
int ret = 0;
F_FILE *file;
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s", "LOG",
day, date, month, ".bin");
file = f_open(fileName , "r");
if(!file) {
ret = 1;
goto END;
}
itemsToRead = f_filelength( fileName );
unsigned char *fileData = malloc(itemsToRead);
if(!fileData) {
ret=2;
goto CLOSE_FILE;
}
readItems = f_read(fileData, 1, itemsToRead, file);
// Maybe not necessary. I don't know. It's up to you.
if(readItems != itemsToRead) {
ret=3;
goto FREE;
}
// Assuming transmit_data have some kind of error check
if(!transmit_data(fileData, itemsToRead)) {
ret=4;
}
FREE:
free(fileData);
CLOSE_FILE:
f_close(file);
END:
return ret;
}
如果一个函数只返回 0,那么返回任何东西都是没有意义的。改为将其声明为无效。现在我使用返回值让调用者能够检测错误和错误类型。