我一直听说在C中你必须真正关注你如何管理记忆.而且我还在开始学习C语言,但到目前为止,我根本不需要做任何内存管理相关活动.我总是想象必须释放变量并做各种丑陋的事情.但事实似乎并非如此.
有人可以给我(带代码示例)一个例子,说明何时需要进行"内存管理"?
有两个地方可以将变量放入内存中.当您创建这样的变量时:
int a; char c; char d[16];
变量在" 堆栈 " 中创建.当堆栈变量超出范围时(即代码无法再访问它们时),它们会自动释放.你可能会听到它们被称为"自动"变量,但这已经过时了.
许多初学者示例仅使用堆栈变量.
堆栈很好,因为它是自动的,但它也有两个缺点:(1)编译器需要事先知道变量有多大,(b)堆栈空间有限.例如:在Windows中,在Microsoft链接器的默认设置下,堆栈设置为1 MB,并非所有变量都可用.
如果你在编译时不知道你的数组有多大,或者你需要一个大的数组或结构,你需要"计划B".
计划B称为" 堆 ".您通常可以创建与操作系统允许的一样大的变量,但您必须自己完成.早期的帖子向您展示了一种可以做到的方法,尽管还有其他方法:
int size; // ... // Set size to some value, based on information available at run-time. Then: // ... char *p = (char *)malloc(size);
(注意堆中的变量不是直接操作的,而是通过指针操作)
一旦你创建了一个堆变量,问题是编译器无法判断你何时完成它,所以你失去了自动释放.这就是您所指的"手动释放"的用武之地.您的代码现在负责决定何时不再需要变量,并释放它以便将内存用于其他目的.对于上述情况,使用:
free(p);
是什么让这第二个选择"令人讨厌的业务"是,知道何时不再需要该变量并不总是很容易.忘记在不需要时释放变量将导致程序消耗更多内存.这种情况称为"泄漏".在程序结束并且操作系统恢复其所有资源之前,"泄露"的内存不能用于任何事情.如果你在实际完成之前错误地释放了一个堆变量,那么即使是更糟糕的问题也是可能的.
在C和C++中,您负责清理堆变量,如上所示.但是,有些语言和环境(如Java和.NET语言,如C#)使用不同的方法,堆可以自行清理.第二种方法,称为"垃圾收集",对开发人员来说要容易得多,但是您需要在开销和性能方面付出代价.这是一个平衡点.
(我已经掩盖了许多细节,以提供更简单,但希望更平坦的答案)
这是一个例子.假设你有一个strdup()函数复制一个字符串:
char *strdup(char *src) { char * dest; dest = malloc(strlen(src) + 1); if (dest == NULL) abort(); strcpy(dest, src); return dest; }
你这样称呼它:
main() { char *s; s = strdup("hello"); printf("%s\n", s); s = strdup("world"); printf("%s\n", s); }
您可以看到该程序有效,但您已经分配了内存(通过malloc)而没有释放它.第二次调用strdup时,您丢失了指向第一个内存块的指针.
对于这么少的内存来说这没什么大不了的,但考虑一下:
for (i = 0; i < 1000000000; ++i) /* billion times */ s = strdup("hello world"); /* 11 bytes */
你现在已经耗尽了11 gig的内存(可能更多,取决于你的内存管理器),如果你没有崩溃你的进程可能运行得很慢.
要修复,您需要在完成使用后使用malloc()为所有内容调用free():
s = strdup("hello"); free(s); /* now not leaking memory! */ s = strdup("world"); ...
希望这个例子有帮助!
当你想在堆而不是堆栈上使用内存时,你必须做"内存管理".如果您不知道在运行时创建数组有多大,那么您必须使用堆.例如,您可能希望将某些内容存储在字符串中,但在程序运行之前不知道其内容的大小.在那种情况下你会写这样的东西:
char *string = malloc(stringlength); // stringlength is the number of bytes to allocate // Do something with the string... free(string); // Free the allocated memory
我认为,在考虑指针在C中的作用时,回答问题的最简洁方法。指针是一种轻量级但功能强大的机制,它为您提供了极大的自由,却以牺牲自己的能力为代价。
在C语言中,确保您的指针指向您拥有的内存的责任仅属于您自己。除非您放弃了指针,否则这需要一种有组织且有纪律的方法,这使得编写有效的C语言变得困难。
迄今为止发布的答案集中在自动(堆栈)和堆变量分配上。使用堆栈分配确实可以实现自动管理和方便的内存,但是在某些情况下(大缓冲区,递归算法),这可能会导致可怕的堆栈溢出问题。确切知道可以在堆栈上分配多少内存在很大程度上取决于系统。在某些嵌入式方案中,几十个字节可能是您的限制,在某些台式机方案中,您可以安全地使用兆字节。
堆分配不是该语言固有的。基本上,它是一组库调用,可以授予您给定大小的内存块的所有权,直到您准备好返回(“释放”)它为止。听起来很简单,但是却伴随着难以言喻的程序员悲伤。问题很简单(两次释放相同的内存,或者根本不释放[内存泄漏],没有分配足够的内存[缓冲区溢出],等等),但是很难避免和调试。严格遵守纪律的方法在实践中绝对是必不可少的,但是当然,语言实际上并没有强制性地要求它。
我想提及另一种类型的内存分配,该类型已被其他帖子忽略。可以通过在任何函数外部声明变量来静态分配变量。我认为一般来说,这种类型的分配会受到不好的说唱,因为它被全局变量使用。但是,没有什么可以说使用这种方式分配的内存的唯一方法就是在混乱的意大利面条代码中将其作为不规则的全局变量。静态分配方法可以简单地用来避免堆和自动分配方法的某些陷阱。一些C程序员惊讶地发现,大型且复杂的C嵌入式和游戏程序是在完全不使用堆分配的情况下构建的。