Do I really need malloc?

Sar*_*enz 3 c malloc mingw

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?

klu*_*utt 6

我真的需要在这里使用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,那么返回任何东西都是没有意义的。将其声明为无效。现在,我使用返回值使调用者可以检测错误和错误类型。