为啥在 C 中需要使用 malloc 进行动态内存分配?
Posted
技术标签:
【中文标题】为啥在 C 中需要使用 malloc 进行动态内存分配?【英文标题】:Why does malloc need to be used for dynamic memory allocation in C?为什么在 C 中需要使用 malloc 进行动态内存分配? 【发布时间】:2021-11-16 10:04:05 【问题描述】:我一直在阅读 malloc 用于动态内存分配。但是如果下面的代码有效...
int main(void)
int i, n;
printf("Enter the number of integers: ");
scanf("%d", &n);
// Dynamic allocation of memory?
int int_arr[n];
// Testing
for (int i = 0; i < n; i++)
int_arr[i] = i * 10;
for (int i = 0; i < n; i++)
printf("%d ", int_arr[i]);
printf("\n");
... malloc 的意义何在?上面的代码不就是一种更易于阅读的动态分配内存的方式吗?
我在另一个 Stack Overflow 的回答中读到,如果某种标志设置为“pedantic”,那么上面的代码会产生编译错误。但这并不能真正解释为什么 malloc 可能是动态内存分配的更好解决方案。
【问题讨论】:
查找stack
和heap
的概念;不同类型的记忆有很多微妙之处。
提示:编写两个附加函数,A
和 B
。以这种方式让A
“分配”内存并返回指向它的指针。做一些涉及函数调用的其他事情(打印内容,从文件中读取其他内容等等),然后将指针从A
传递到B
并让B
从中读取。看看基于堆栈的分配有多么有用。
因为VLA are problematic
"I read on another Stack Overflow answer that if some sort of flag is set to "pedantic", then the code above would produce a compile error."
-- 它不会在 C 中产生编译器警告/错误,但在 C++ 中会产生,如果您在 ISO 兼容模式下编译(-std=C++20 -pedantic
带有 gcc 和 clang 的命令行参数)。这是因为VLAs 是 ISO C 的一部分,而不是 ISO C++。
What gets allocated on the stack and the heap?
【参考方案1】:
查找stack
和heap
的概念;不同类型的内存有很多微妙之处。函数内部的局部变量存在于stack
中,并且只存在于函数内部。
在您的示例中,int_array
仅在其定义的函数的执行尚未结束时才存在,您无法在函数之间传递它。你不能返回 int_array
并期望它起作用。
malloc()
用于创建存在于 heap 上的内存块。 malloc
返回指向此内存的指针。这个指针可以作为变量(例如return
ed)从函数中传递,并且可以在程序中的任何地方使用来访问您分配的内存块,直到您free()
它。
示例:
'''C
int main(int argc, char **argv)
int length = 10;
int *built_array = make_array(length); //malloc memory and pass heap pointer
int *array = make_array_wrong(length); //will not work. Array in function was in stack and no longer exists when function has returned.
built_array[3] = 5; //ok
array[3] = 5; //bad
free(built_array)
return 0;
int *make_array(int length)
int *my_pointer = malloc( length * sizeof int);
//do some error checking for real implementation
return my_pointer;
int *make_array_wrong(int length)
int array[length];
return array;
'''
注意:
有很多方法可以完全避免使用malloc
,方法是在调用者中预先分配足够的内存等。推荐用于嵌入式和安全关键程序,您希望确保永远不会用完记忆。
【讨论】:
int_array
不仅仅存在于main
中,它可以传递给其他函数。它只存在当它定义的函数的执行没有结束;不仅仅是在执行 in 该函数时。该函数可以通过地址将数组传递给其他例程。 (在 C 标准的术语中,调用另一个例程会暂时挂起调用例程的执行,但不会结束它。)【参考方案2】:
仅仅因为某些东西看起来更漂亮并不能使它成为更好的选择。
VLA 存在一长串问题,其中最重要的是它们不足以替代堆分配的内存。
主要的 - 也是最重要的 - 原因是 VLA 不是持久动态数据。也就是说,一旦你的函数终止,数据就会被回收(它存在于堆栈中,所有地方!),这意味着任何其他仍然挂在它上面的代码都是 SOL。
您的示例代码没有遇到这个问题,因为您没有在本地上下文之外使用它。继续尝试使用 VLA 构建二叉树,然后添加一个节点,然后创建一个新树并尝试将它们都打印出来。
下一个问题是堆栈不是分配大量动态数据的合适位置——它适用于函数帧,它们的起始空间有限。全局内存池 OTOH 专为此类用途而设计和优化。
提出问题并尝试理解事物是件好事。只是要小心,不要相信自己比许多人更聪明,他们利用现在近 80 年的经验来设计和实施真正运行已知宇宙的系统。如此明显的缺陷在很久很久以前就会被立即识别并在我们两个人出生之前就被消除了。
VLA 有自己的位置,但可惜它很小。
【讨论】:
您好,在这里,很好的答案 - 谢谢。相信我,当我说我有一刻不相信我会奇迹般地发现某种逻辑缺陷或类似的东西!我知道 malloc 存在一定是有原因的,只是想了解那个原因是什么。【参考方案3】:声明局部变量会从堆栈中获取内存。这有两个后果。
-
一旦函数返回,该内存就会被销毁。
堆栈内存有限,用于所有局部变量以及函数返回地址。如果分配大量内存,就会遇到问题。仅用于少量内存。
【讨论】:
当函数返回时,它们的内存被释放,而不是被销毁。 C++ 有一个销毁对象的概念,但在 C 中不会发生这种情况。内存只是被释放。有的同学不知从何而来,释放的内存被清除了。我们应该避免给人这样的印象。【参考方案4】:当您的功能代码中有以下内容时:
int int_arr[n];
这意味着你在函数堆栈上分配了空间,一旦函数返回,这个堆栈将不复存在。
想象一个需要将数据结构返回给调用者的用例,例如:
Car* create_car(string model, string make)
Car* new_car = malloc(sizeof(*car));
...
return new_car;
现在,一旦函数完成,您仍然会拥有汽车对象,因为它是在堆上分配的。
【讨论】:
【参考方案5】:int int_arr[n]
分配的内存只保留到例程执行结束(当它返回或以其他方式终止时,如setjmp
)。这意味着您不能以一种顺序分配事物并以另一种顺序释放它们。您不能分配临时工作缓冲区,在计算一些数据时使用它,然后为结果分配另一个缓冲区,然后释放临时工作缓冲区。要释放工作缓冲区,你必须从函数中返回,然后结果缓冲区将被释放到。
使用自动分配,您不能从文件中读取,为从文件中读取的每个内容分配记录,然后乱序删除一些记录。您根本无法动态控制分配的内存;自动分配被强制采用严格的后进先出 (LIFO) 顺序。
您不能编写分配内存、初始化和/或执行其他计算并将分配的内存返回给调用者的子例程。
(也有人会指出,自动对象常用的栈内存一般限制在1-8MB,而动态分配的内存一般要大得多。不过,这是常用设置选择的神器使用并且可以更改;它不是自动分配与动态分配的本质所固有的。)
【讨论】:
【参考方案6】:如果分配的内存很小并且只在函数内部使用,那么 malloc 确实是不必要的。 如果内存量非常大(通常是MB或更多),上面的例子可能会导致堆栈溢出。 如果函数返回后内存还在使用,则需要malloc或全局变量(静态分配)。
请注意,某些编译器可能不支持通过上述局部变量进行动态分配。
【讨论】:
以上是关于为啥在 C 中需要使用 malloc 进行动态内存分配?的主要内容,如果未能解决你的问题,请参考以下文章
C语言中已经有了malloc和free,为啥还需要new和delete?