I understand that malloc is used to dynamically allocate memory. In my code, I have the following function that I sometimes call:
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;
}
Run Code Online (Sandbox Code Playgroud)
As you may see, the number of items I read from the file can be different each time. The line
unsigned char fileData[itemsToRead]; is used to read these variable sized files. I can see that I am allocating memory dynamically in some way. This function works fine. Do I really need to use malloc here?
Is there anything wrong with the way I am declaring this array?
我真的需要在这里使用malloc吗?我声明此数组的方式有什么问题吗?
这取决于。VLA:s已从C11中删除为必需组件,因此严格来说,您正在使用编译器扩展,从而降低了可移植性。将来,VLA:s可能(机会极低)会从编译器中删除。也许您还想在不支持VLA:s的情况下在编译器上重新编译代码。有关此的风险分析由您决定。
另一个问题是分配是否失败。如果您使用的是malloc,则有机会从中恢复,但是如果您只想执行以下操作:
unsigned char *fileData = malloc(itemsToRead);
if(!fileData)
exit(EXIT_FAILURE);
Run Code Online (Sandbox Code Playgroud)
也就是说,仅在出现故障时退出而不尝试恢复,那么这并不重要。至少从纯粹的恢复角度来看并非如此。
而且,尽管C标准没有要求VLA:s必须最终出现在堆栈或堆上,但据我所知,将它们放在堆栈上是很常见的。这意味着由于可用内存不足而导致分配失败的风险要高得多。在Linux上,堆栈通常为8MB,在Windows上通常为1MB。在几乎所有情况下,可用堆都高得多。该声明与运算符的工作原理char arr[n]基本相同。char *arr = alloca(n)sizeof
VLA:s不能代替malloc。它们是的替代品alloca。如果您不想将更malloc改为alloca,则也不应更改为VLA。
另外,在许多情况下,VLA似乎是一个好主意,但检查大小是否低于某个限制也是一个好主意,例如:
int foo(size_t n)
{
if(n > LIMIT) { /* Handle error */ }
int arr[n];
/* Code */
}
Run Code Online (Sandbox Code Playgroud)
那会起作用,但将其与此进行比较:
int foo(size_t n)
{
int *arr = malloc(n*sizeof(*arr));
if(!arr) { /* Handle error */ }
/* Code */
free(arr);
}
Run Code Online (Sandbox Code Playgroud)
您并没有真正使事情变得那么容易。这仍然是一个错误检查,因此真正要摆脱的唯一事情就是free调用。我还可以补充一点,由于大小太大,VLA分配失败的风险要高得多。因此,如果您知道大小很小,则不需要检查,但是再次,如果您知道大小很小,只需使用适合您需要的常规数组即可。
有很多事情要考虑,但是我会避免使用VLA:s。如果您问我,它们的最大风险是由于它们易于使用,因此人们对它们变得粗心。对于那些我认为它们合适的少数情况,我会alloca改用,因为那样的话我就不会隐藏危险。
C11和更高版本不需要VLA :,因此严格来说,您依赖编译器扩展。
VLA是alloca而不是的语法糖malloc。因此,请勿使用它们代替malloc。除了sizeof在VLA上的工作方式外,它们只提供一点简单的声明,根本没有任何好处。
VLA(通常)存储在堆栈中,而完成我的malloc的分配通常存储在堆中,因此,较大的分配失败的风险更大。
您无法检查VLA分配是否失败,因此最好事先检查大小是否太大。但是然后我们有一个错误检查,就像检查malloc返回的NULL一样。
此功能工作正常。
不,不是的。它具有未定义的行为。正如乔纳森·莱夫勒(Jonathan Leffler)在评论中指出的那样,数组fileName太短了。包含\0-terminator 至少需要12个字节。您可以更改为:
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s",
"LOG", day, date, month, ".bin");
Run Code Online (Sandbox Code Playgroud)
在这种情况下,数组太小的问题将通过创建带有扩展名的文件来表现出来.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;
}
Run Code Online (Sandbox Code Playgroud)
如果一个函数只返回0,那么返回任何东西都是没有意义的。将其声明为无效。现在,我使用返回值使调用者可以检测错误和错误类型。