我啥时候应该在 C 中使用 malloc,啥时候不应该?

Posted

技术标签:

【中文标题】我啥时候应该在 C 中使用 malloc,啥时候不应该?【英文标题】:When should I use malloc in C and when don't I?我什么时候应该在 C 中使用 malloc,什么时候不应该? 【发布时间】:2010-12-30 03:28:06 【问题描述】:

我了解 malloc() 的工作原理。我的问题是,我会看到这样的事情:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

为了简洁起见,我省略了错误检查。我的问题是,您不能通过初始化指向内存中某个静态存储的指针来完成上述操作吗?也许:

char *some_memory = "Hello World";

什么时候你真正需要自己分配内存而不是声明/初始化你需要保留的值?

【问题讨论】:

回复:为了简洁起见,我省略了错误检查 - 不幸的是,太多的程序员省略了错误检查,因为他们没有意识到malloc() 可以失败! 【参考方案1】:
char *some_memory = "Hello World";

正在创建一个指向字符串常量的指针。这意味着字符串“Hello World”将位于内存的只读部分中,而您只有一个指向它的指针。您可以将字符串用作只读。您不能对其进行更改。示例:

some_memory[0] = 'h';

自找麻烦。

另一方面

some_memory = (char *)malloc(size_to_allocate);

正在分配一个 char 数组(一个变量),而 some_memory 指向该分配的内存。现在这个数组既可读又可写。你现在可以这样做:

some_memory[0] = 'h';

数组内容变为“hello World”

【讨论】:

只是为了澄清一下,尽管我很喜欢这个答案(我确实给了你+1),但你可以在没有 malloc() 的情况下通过使用字符数组来做同样的事情。类似于: char some_memory[] = "Hello"; some_memory[0] = 'W';也可以。 你是对的。你可以这样做。当您使用 malloc() 时,内存是在运行时动态分配的,因此您无需在编译时修复数组大小,您也可以使用 realloc() 使其增长或缩小 当您这样做时,这些事情都无法完成: char some_memory [] = "你好";在这里,即使您可以更改数组的内容,它的大小也是固定的。因此,根据您的需要,您可以使用以下三个选项之一:1)指向 char const 的指针 2)动态分配的数组 3)固定大小,编译时分配的数组。 为了强调它是只读的,你应该写const char *s = "hi"; 这难道不是标准要求的吗? @Till,不,因为您声明了一个初始化为字符串文字“hi”的基地址的指针。 s 可以完全合法地重新分配以指向非常量字符。如果你想要一个指向只读字符串的常量指针,你需要const char const* s;【参考方案2】:

对于那个确切的例子,malloc 没什么用。

需要 malloc 的主要原因是当您的数据必须具有与代码范围不同的生命周期时。您的代码在一个例程中调用 malloc,将指针存储在某处并最终在另一个例程中调用 free。

第二个原因是 C 无法知道堆栈上是否有足够的空间用于分配。如果您的代码需要 100% 健壮,则使用 malloc 更安全,因为这样您的代码可以知道分配失败并进行处理。

【讨论】:

内存生命周期以及何时以及如何释放它的相关问题是许多常见库和软件组件的重要问题。他们通常有一个有据可查的规则:“如果你传递一个指向我的例程中的 this 的指针,你需要对其进行 malloc 处理。我会跟踪它,并在何时释放它我已经完成了。”讨厌的错误的一个常见来源是将指向静态分配内存的指针传递给这样的库。当库试图 free() 它时,程序崩溃。我最近花了很多时间来修复一个其他人写的错误。 你是说 malloc() 实际使用的唯一时间是在程序生命周期中有一段代码将被多次调用并且需要多次调用时被“清除”,因为 malloc() 伴随着 free()?例如,在像命运之轮这样的游戏中,在您猜测后,将输入放入指定的 char 数组中,malloc() 大小的数组可以被释放以供下一次猜测? 数据的生命周期确实是使用malloc的真正原因。假设一个抽象数据类型由一个模块表示,它声明一个列表类型,以及从列表中添加/删除项目的例程。这些项目值需要复制到动态分配的内存中。 @Bob:那些讨厌的错误,让分配器释放内存的约定要好得多,毕竟你可能正在回收它。假设您使用 calloc 分配内存以改善引用的局部性,这暴露了这些库的损坏性质,因为您只需要为整个块调用一次 free 。幸运的是,我不必使用将内存指定为“malloc-ed”的库,这不是 POSIX 传统,很可能被视为错误。如果他们“知道”你必须使用 malloc,为什么库程序不为你做呢?【参考方案3】:

malloc 是在运行时分配、重新分配和释放内存的绝佳工具,与像你的 hello world 示例这样的静态声明相比,静态声明在编译时处理,因此无法更改大小。

因此,当您处理任意大小的数据(例如读取文件内容或处理套接字)并且您不知道要处理的数据的长度时,Malloc 始终很有用。

当然,在你给出的一个简单的例子中,malloc 不是神奇的“正确工作的正确工具”,但对于更复杂的情况(例如在运行时创建任意大小的数组),它是唯一的出路。

【讨论】:

【参考方案4】:

如果您不知道需要使用的内存的确切大小,则需要动态分配 (malloc)。例如,当用户在您的应用程序中打开文件时。您需要将文件的内容读入内存,但您当然不会提前知道文件的大小,因为用户会在运行时当场选择文件。所以基本上你需要malloc,当你事先不知道你正在使用的数据的大小时。至少这是使用malloc 的主要原因之一。在您的示例中,您使用了一个在编译时已经知道其大小的简单字符串(而且您不想修改它),动态分配它没有多大意义。


有点跑题了,但是...在使用malloc 时必须非常小心不要造成内存泄漏。考虑这段代码:

int do_something() 
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;

您知道这段代码有什么问题吗?在mallocfree 之间有一个条件返回语句。起初可能看起来不错,但请考虑一下。如果出现错误,您将在不释放分配的内存的情况下返回。这是内存泄漏的常见来源。

当然这是一个非常简单的例子,在这里很容易看出错误,但是想象一下数百行代码中到处都是指针、mallocs、frees,以及各种错误处理。事情很快就会变得非常混乱。这是在适用情况下我更喜欢现代 C++ 而不是 C 的原因之一,但这是另一个话题。

因此,每当您使用malloc 时,请始终确保您的内存尽可能为freed。

【讨论】:

很好的例子!一路走好^_^【参考方案5】:
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

是非法的,字符串字面量是const

这将在堆栈上或全局分配一个 12 字节的 char 数组(取决于它的声明位置)。

char some_memory[] = "Hello World";

如果您想为进一步的操作留出空间,您可以指定数组的大小应该更大。 (不过,请不要将 1MB 放在堆栈上。)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);

【讨论】:

【参考方案6】:

需要分配内存的一个原因是如果您想在运行时修改它。在这种情况下,可以使用堆栈上的 malloc 或缓冲区。将“Hello World”分配给指针的简单示例定义了“通常”无法在运行时修改的内存。

【讨论】:

以上是关于我啥时候应该在 C 中使用 malloc,啥时候不应该?的主要内容,如果未能解决你的问题,请参考以下文章

我啥时候应该在 C++ 中使用 typedef?

我啥时候应该在 Twitter Bootstrap 中使用容器和行?

我啥时候应该使用 Vuex?

我啥时候应该使用“while 循环”?

我啥时候应该在课堂上使用“this”?

我啥时候应该在同一个视图控制器中使用多个视图?