何时应该在C语言中使用malloc,何时不需要使用?

98

我理解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";

在什么情况下,你需要手动分配内存,而不是声明/初始化需要保留的值?


7
回复:我为了简洁而省略了错误检查 - 不幸的是,太多程序员因为没有意识到malloc()可能会失败而省略了错误检查 - Andrew
6个回答

138
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"


21
只是为了澄清,虽然我很喜欢这个答案(我已经给你+1了),但你可以通过使用字符数组而不是malloc()来做到同样的事情。像这样:char some_memory[] = "Hello"; some_memory[0] = 'W'; 也可以工作。 - randombits
20
你是对的。你可以这样做。当你使用malloc()时,内存是在运行时动态分配的,所以你不需要在编译时固定数组大小,而且你可以使用realloc()来使它增长或缩小。这些事情都不能在char some_memory[] = "Hello";中实现。在这里,即使你可以改变数组的内容,但它的大小是固定的。因此,根据你的需求,你可以使用以下三种选项之一:1) char const指针 2) 动态分配的数组 3) 固定大小、编译时分配的数组。 - codaddict
为了强调它是只读的,应该写成 const char *s = "hi";。这实际上是标准所要求的吗? - Till Theis
1
@Till,不是因为你声明了一个指针并将其初始化为字符串字面值“hi”的基地址。s可以被重新分配,以合法地指向非const char。如果您想要一个指向只读字符串的常量指针,则需要使用const char const* s; - Rob11311

39

对于这个具体的例子,malloc的用处很小。

malloc主要用于当你的数据必须具有与代码作用域不同的生命周期时。你的代码在一个例程中调用malloc,将指针存储在某个位置,最终在另一个例程中调用free。

次要原因是C语言无法知道堆栈上是否有足够的空间进行分配。如果你的代码需要100%的健壮性,使用malloc会更安全,因为这样你的代码可以知道分配失败并加以处理。


4
内存生命周期及其相关问题,包括何时以及如何释放内存,是许多常用的库和软件组件中的重要问题。它们通常有一个很好的文档规则:“如果您将指向this之一的指针传递给我的某个例程,则需要对其进行malloc分配。我会跟踪它,并在完成后释放它。”一个常见的恶性错误源是将指向静态分配内存的指针传递给此类库。当库尝试free()时,程序会崩溃。最近我花了很多时间修复了别人写的这样一个错误。 - Bob Murphy
你是说malloc()实际上只有在程序生命周期内会被多次调用并需要被“清除”的代码段中才会被使用,因为malloc()需要与free()一起使用吗?例如,在像幸运轮盘这样的游戏中,当你猜测并将输入放入指定的char数组后,malloc()大小的数组可以被释放以进行下一次猜测? - Smith Will Suffice
数据的生命周期确实是使用malloc的真正原因。假设一个抽象数据类型由一个模块表示,它声明了一个List类型,并提供了向列表中添加/删除项目的例程。这些项目值需要复制到动态分配的内存中。 - Rob11311
@Bob:那些令人讨厌的错误,使得分配器释放内存的约定变得更加出色,毕竟你可能正在回收它。假设您使用calloc分配内存以提高引用局部性,那么这会暴露这些库的不良特性,因为您需要为整个块调用一次free。幸运的是,我没有使用过指定必须“malloc”的库,这不是POSIX传统,很可能被认为是错误。如果他们“知道”您必须使用malloc,为什么库例程不为您执行它呢? - Rob11311

18

malloc是一种在运行时分配、重新分配和释放内存的绝妙工具,与像您的“Hello World”示例这样的静态声明相比,在编译时处理并且大小不能更改。

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

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


8
如果你不知道需要使用的内存确切大小,你需要进行动态分配(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之间有一个条件返回语句。一开始可能看起来没问题,但仔细考虑一下。如果出现错误,你将返回而不释放你分配的内存。这是内存泄漏的常见来源。
当然,这只是一个非常简单的例子,很容易发现错误,但是想象一下有数百行代码中到处都是指针、mallocfree和各种错误处理。事情很快就会变得非常混乱。这是我更喜欢现代C++而不是C的适用场景之一,但那是另一个话题。
因此,每当使用malloc时,请确保你的内存尽可能被释放。

太棒了!走得好 ^_^ - Musa Al-hassy

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

5

在运行时修改内存时,需要分配内存。此时可以使用malloc或堆栈缓冲区。将“Hello World”赋值给指针的简单示例定义了“通常”无法在运行时修改的内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接