我真的需要Malloc吗?

dphi5xsq  于 2023-10-16  发布在  其他
关注(0)|答案(2)|浏览(135)

我知道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吗?我声明这个数组的方式有什么问题吗?

xoefb8l8

xoefb8l81#

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);

也就是说,只是在失败时退出,而不是试图恢复,那么这并不重要。至少从纯粹的恢复Angular 来看不是这样。
而且,尽管C标准没有强制要求VLA:s必须放在堆栈或堆上,但据我所知,将它们放在堆栈上是很常见的。这意味着由于可用内存不足而导致分配失败的风险要高得多。在Linux上,堆栈通常为8 MB,在Windows上为1 MB。在几乎所有情况下,可用堆都要高得多。声明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分配失败的风险要高得多。因此,如果你知道大小很小,检查是不必要的,但话又说回来,如果你知道它很小,只是使用一个常规的数组,将适合你的需要。
但是,我不否认VLA的一些优点。你可以读到他们的here.,但海事组织,虽然他们有这些优势,他们是不值得的。当你发现VLA:s有用的时候,我会说你至少应该考虑切换到另一种语言。
此外,VLA:s(以及alloca)的一个优点是它们通常比malloc更快。因此,如果您有性能问题,您可能希望切换到alloca而不是mallocmalloc调用涉及向操作系统(或类似的东西)请求一块内存。然后操作系统搜索它,并返回一个指针,如果它找到它。另一方面,一个alloca调用通常只是通过在一条cpu指令中改变堆栈指针来实现的。
有很多事情要考虑,但我会避免使用VLA:s。如果你问我,他们最大的风险是,因为他们是如此容易使用,人们变得粗心与他们。对于我认为合适的少数情况,我会使用alloca,因为这样我就不会隐藏危险。

简短总结

  • VLA:s在C11和更高版本中是不需要的,所以严格地说,您依赖于编译器扩展。但是,alloca也是如此。所以如果这是一个非常大的问题,如果你不想使用malloc,请使用固定数组。

  • VLA:s是alloca的语法糖(不是100%正确,特别是在处理多维数组时),而不是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的文件来表现出来,这是一个比未定义行为更好的bug,这就是当前的情况。
你的代码中也没有错误检查。我会这样重写。对于那些认为后藤不好的人来说,好吧,它通常是不好的,但是错误处理既实用又被经验丰富的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;
}

如果一个函数总是返回相同的值,那么返回任何值都是没有意义的。将其分解为void。现在我使用返回值使调用者能够检测错误和错误类型。

yvfmudvl

yvfmudvl2#

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

相关问题