C语言 我真的需要马洛克吗?



我知道malloc是用来动态分配内存的。在我的代码中,我有以下函数,我有时会调用它:

int memory_get_log(unsigned char day, unsigned char date, unsigned char month){
char fileName[11];
unsigned long readItems, itemsToRead;
F_FILE *file;
sprintf(fileName, "%s_%u%u%u%s", "LOG", day, date, month, ".bin");
file = f_open(fileName , "r");
itemsToRead = f_filelength( fileName );
//unsigned char *fileData = (unsigned char *) malloc(itemsToRead);
unsigned char fileData[itemsToRead]; //here I am not using malloc
readItems = f_read(fileData, 1, itemsToRead, file);
transmit_data(fileData, itemsToRead);
f_close(file);
return 0;
}

如您所见,我每次从文件中读取的项目数可能不同。该行unsigned char fileData[itemsToRead];用于读取这些可变大小的文件。我可以看到我正在以某种方式动态分配内存。此函数工作正常。我真的需要在这里使用 malloc 吗? 我声明此数组的方式有什么问题吗?

TL;博士

如果您不知道自己在做什么,请在所有情况下使用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有一些优点。您可以在此处阅读有关它们的信息。但是IMO,虽然他们有这些优势,但不值得。每当你发现VLA:s有用时,我会说你至少应该考虑切换到另一种语言。

此外,VLA:s(以及alloca)的一个优点是它们通常比malloc快。因此,如果您遇到性能问题,则可能需要切换到alloca而不是mallocmalloc调用涉及向操作系统(或类似的东西)请求一段内存。然后,操作系统会搜索该指针,如果找到它,则返回一个指针。另一方面,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在评论中指出的那样,数组fileName太短。它至少需要 12 个字节才能包含-终止符。您可以通过更改为以下内容来使其更安全:

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,那么返回任何东西是没有意义的。改为将其声明为无效。现在,我使用返回值使调用方能够检测错误和错误类型。

首先,"unsigned char fileData[itemsToRead]"一行要求堆栈上的内存,如果文件大小很大,这将是一个可怕的错误。您应该使用"malloc"来请求堆上的内存。 其次,如果文件大小真的足够大,你应该使用虚拟内存或动态加载,如"fseek"方法。

最新更新