TL;DR
如果您不知道自己在做什么,请使用malloc
或在所有情况下都是固定大小的数组。 VLA:根本没有必要。请注意,VLA:s 不能是静态的也不能是全局的。
我真的需要使用吗malloc
here?
是的。您正在读取一个文件。它们通常比适合 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
操作员工作。
虽然我可以理解您可能想使用sizeof
有时,我发现很难找到真正需要它的人。毕竟,大小永远不会改变,并且在进行分配时大小是已知的。所以而不是:
int arr[n];
...
for(int i=0; i<sizeof(arr), ...
Just do:
const size_t size = n;
int arr[size];
...
for(int i=0; i<size; ...
VLA:s 不能替代malloc
。它们是替代品alloca
。如果您不想更改malloc
to an 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. https://stackoverflow.com/a/42094467/6699433但在我看来,虽然他们有这些优势,但并不值得。每当您发现 VLA:s 有用时,我会说您至少应该考虑切换到另一种语言。
此外,VLA:s 的优点之一(还有alloca
)是它们通常比malloc
。因此,如果您遇到性能问题,您可能需要切换到alloca
代替malloc
. A malloc
调用涉及向操作系统(或类似的系统)请求一块内存。然后操作系统会搜索该指针,如果找到则返回一个指针。一个alloca
另一方面,call 通常只是通过更改单个 cpu 指令中的堆栈指针来实现。
有很多事情需要考虑,但我会避免使用 VLA:s。如果你问我,它们最大的风险是,由于它们很容易使用,人们对它们变得粗心。对于我认为合适的少数情况,我会使用alloca
相反,因为这样我就不会隐藏危险。
简短的摘要
-
C11 及更高版本不需要 VLA:s,因此严格来说,您依赖于编译器扩展。然而,对于alloca
。因此,如果这是一个非常大的问题,如果您不想使用,请使用固定数组malloc
.
-
VLA:s 是语法糖(不是 100% 正确,尤其是在处理多维数组时)alloca
并不是malloc
。所以不要用它们代替malloc
。除了如何sizeof
在 VLA 上工作,除了稍微简单的声明之外,它们绝对没有任何好处。
-
VLA:s(通常)存储在堆栈上,而 malloc 完成的分配(通常)存储在堆上,因此大分配失败的风险要高得多。
-
您无法检查 VLA 分配是否失败,因此最好提前检查大小是否太大。但是接下来我们会进行错误检查,就像检查是否一样malloc
返回 NULL。
-
VLA 不能是全局的也不能是静态的。单独的静态部分可能不会引起任何问题,但如果你想要一个全局数组,那么你不得不使用malloc
或固定大小的数组。
这个功能运行良好。
不,不是的。它具有未定义的行为。正如 Jonathan Leffler 在评论中指出的那样,该数组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;
goto FREE; // Just in case you add another if statement below
// and forget to add this
}
FREE:
free(fileData);
CLOSE_FILE:
f_close(file);
END:
return ret;
}
如果一个函数总是返回相同的值,那么返回任何东西都是毫无意义的。相反,将其声明为无效。现在我使用返回值使调用者能够检测错误和错误类型。