什么是内存泄漏?

29

显然,维基百科上有关于这个主题的大量信息,但我想确保我理解得正确。从我所了解的来看,了解堆栈关系才能真正理解内存泄漏的重要性?

因此,这是我(所认为)了解的内容,欢迎指出错误!

在程序启动时,一块内存被分配,比如0x000到0xFFF。第一部分(比如0x000到0x011)是代码/文本段,用于加载程序代码。

+--------------+ 0x011
| Program Code |
+--------------+ 0x000

然后你有一个栈(假设是0x012到0x7ff),它保存局部变量,并以先进先出的方式存储/检索它们。所以,如果你有类似于

char middleLetter(string word){
     int len = word.length();
     return word[len/2];
}

int main(){
   int cool_number;
   char letter;
   letter = middleLetter("Words");
   ...

那么你的变量将在堆栈上分配,它会像这样:

+-------------+ 0x7ff
|             |
|             |
|             |
| ...         |
| len         |
| letter      |
| cool_number |
+-------------+ 0x012

当然,如果您在某个地方分配了内存(使用mallocnew),但从未释放它,则您的堆可能会出现这样的情况,并且现在您有一个内存泄漏:

+-------------+ 0xfff
|             |
| malloc(20)  | 0xf64
| malloc(50)  | 0xf32
| malloc(50)  | 0xf00
| ...         |
|             |
+-------------+ 0x800

这意味着虽然你可以通过指针算术直接访问0xf32,但操作系统/你的程序认为内存位置0xf00-0xf46已经被占用,并且不会再次将这些位置用于存储,直到你的程序关闭并释放内存。但是共享内存呢?维基百科称它不会被释放(直到计算机重新启动?)。那么如何知道它是共享内存呢?

这是一个相当不错的基本理解吗?我有没有漏掉或搞错什么?感谢您的关注!


2
应该是社区维基 - SilentGhost
8
为什么?这是一个合法的,虽然比较基础的问题,而且有明确的答案。 - David Thornley
9个回答

10

看起来你确实理解了它 - 只有一个例外:在你的例子中,len 是像其他所有变量一样的堆栈变量。newmalloc在堆上创建,其他所有变量(例如局部变量等)都在堆栈上。而且 main 函数的局部变量与任何其他函数的变量没有区别。

共享内存是相当罕见的情况,通常不需要它,除非你明确要求它(否则,某些随机的其他进程可能使用与你的进程使用完全相同的内存 - 显然,这会严重破坏事情)。


这是我最初的想法,但是这个网站似乎说得不同?http://www.maxi-pedia.com/what+is+heap+and+stack - Wayne Werner
@Wayne:就我所知,那篇文章是垃圾。如果你足够了解真假陈述和那些只是暗示奇怪事情的陈述之间的区别,那么你已经足够了解不需要阅读那种基础的东西。你最好在这里提出像你这样的问题,并仔细阅读答案和答案评论。 - David Thornley
1
嗯,栈内存和堆内存的东西一直让我很困惑——哪个做什么——所以即使它让我感觉不对劲,我也没信心去质疑它。然而,由于我收到了众多答案以及维基百科文章的影响,我认为我已经知道足够的知识去在下次识破谎言了 ;) - Wayne Werner

7

你的函数变量通常也在堆栈上,而不是堆上。在大多数系统中,堆用于动态分配。通常的内存泄漏情况是:

  1. 调用某个函数F
  2. F分配(new或malloc)一些内存
  3. F返回给调用者(没有delete/free)
  4. 指向动态分配内存的指针超出作用域
    • 内存仍然分配。
    • 您无法再删除/释放它

6

内存泄漏简单解析: 每当你使用malloc/new分配内存并在使用完该内存后不使用free/delete进行释放时,就会导致内存泄漏! 分配的内存将一直存在,并且该空间将永远不会被程序再次使用。

当泄漏发生在被多次调用的函数上时,这是一个严重的问题,因为每次调用函数时,泄漏都会变得越来越大。


如果内存泄漏增长缓慢,那么很难发现,如果泄漏的对象在需要删除之前被用于多个位置,则调试也很困难! - Brian S

4
以这种方式思考。在使用需要编码人员管理内存的语言进行开发时,您需要为程序将要使用的每个单独对象明确分配和销毁内存。如果您没有正确创建某些内容,则很容易知道,因为您的程序将无法正常工作。但是,找到和调试未正确销毁对象的情况(这称为内存泄漏)则更加困难。
让我们以典型应用程序为例,比如一个RSS新闻阅读器。在这样的应用程序中,通常存在许多循环(循环浏览不同的RSS源、不同的RSS条目、RSS标签等等)。如果您创建的对象没有被正确销毁(或释放),那么每次运行“泄漏”代码时,您都会在内存中留下另一个废弃的对象。如果循环运行1,000次,则会有1,000个废弃的对象占用空间。您可以看到,这样很快就会消耗宝贵的资源。

3

一般来说,函数(子程序)中的自动变量也将存储在堆栈上。只有使用“malloc”或“new”数据分配才来自堆。接下来,基于堆的分配可以在程序结束之前被释放和重复使用(多次)。分配系统跟踪正在使用的区域和已释放的区域。最后,内存泄漏是指您的程序在未释放分配的某些内存时失去了其跟踪。这可能是通过将新值写入指针或将指针存储在生存期/作用域有限的变量中而发生。


3
看起来您正在使用C++代码。在C++中,局部变量被放置在堆栈上(我猜想全局变量也是如此,但我不确定)。所以,在您的middleLetter函数中,len也会被放置在调用堆栈上。我建议阅读这篇文章:http://en.wikipedia.org/wiki/Call_stack 当您使用new运算符与类型一起使用时,例如int *x = new int;,足够的连续内存被找到以放置一个int。但是,用于引用它的指针*x却是一个局部变量。如果x超出范围并且您失去了指针,则不会释放堆上的内存。尽管你现在无法引用它,但该内存仍然被你的程序“使用”。因为您无法引用它,因此无法解除分配它(或删除它)。
随着您继续这样做,您最终会耗尽可以分配给堆的空间,您的程序将越来越接近崩溃,因为它没有可用的内存等其他问题。

那是一篇很棒的文章。我认为我对栈有了更具体的理解。 - Wayne Werner
1
全局变量(包括static变量)的存储位置并没有明确定义。它们可能在堆栈底部,也可能在它们自己的小区域内。由于它们(至少在C和C++中)被初始化,所以很有可能是它们自己的小区域。堆栈是通过调用函数创建变量的地方,而不一定是一个通用的临时存储区。 - David Thornley

2

内存泄漏是指您分配的动态内存却从未释放。这会导致程序随着时间的推移慢慢消耗内存,如果物理内存完全用尽并且交换空间也被完全占用,可能会导致崩溃。

因此,这在技术上就是一种内存泄漏:

int main(void) {
    void *some_memory = malloc(400);

    // rest of program...
    // some_memory is never freed
}

但这不是:
int main(void) {
    void *some_memory = malloc(400);

    // rest of program...
    free(some_memory);
}

当然,这只是一个有点琐碎的例子。更常见的是,在循环中发生内存泄漏,其中分配了几个内存块,但并非所有内存都被释放。一个常见的例子是,如果您有一个包含指向动态分配字符串的指针的动态分配结构体——如果您释放结构体但忘记释放字符串,则会发生内存泄漏。但请注意,当程序终止时,操作系统将清理任何分配的内存,因此它不会永久影响您的内存。
使用malloc()、calloc()和realloc()等函数动态分配内存,但不使用free()将导致内存泄漏。当您动态分配内存时,它存储在堆上而不是栈上(栈用于静态内存分配,例如本地变量,而堆用于动态内存分配)。当函数返回时,堆栈内存上的值会被回收。但是,堆内存不会被回收。
最后,泄漏的内存是不再可访问且未被释放/解除分配的内存。

0

当程序员在堆中创建内存并忘记删除它时,就会出现内存泄漏。内存泄漏会降低计算机的性能并减少可用的动态内存量。简而言之,当程序请求更多内存而不释放不再需要的内存时,就会发生内存泄漏。因此,当你完成内存分配后,需要使用free()函数释放内存。


0
在垃圾回收系统中,“内存泄漏”这个术语有时似乎有点模糊。我提供以下定义,它同样适用于使用显式释放和垃圾回收的系统:
如果存在一个初始输入序列S和重复输入模式P,使得程序或子程序P满足以下条件,则程序或子程序P具有内存泄漏:
1.将输入S后跟P提供给程序或子程序将使程序处于与P之前相同的“有意义”状态,但是 2.对于任何数量的内存Q,存在一些重复次数N,使得将输入S和N次P提供给程序将导致程序使用超过数量Q的内存。
如果所有输入实际上都有助于程序的状态,则没有内存泄漏的程序可能具有相对于输入大小无限制的内存使用量。例如,读取输入文件并以相反顺序写出的程序将需要足够的内存来保存文件。另一方面,具有内存泄漏的程序将需要无限量的内存来处理无限输入流,即使所需的内存量应该是有限的。

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