堆内存分配

如果我使用malloc()在我的程序中动态分配内存但是我没有在程序运行时释放内存,那么在程序终止后是否会释放动态分配的内存?

或者如果它没有被释放,并且我一遍又一遍地执行相同的程序,它每次都会分配不同的内存块吗? 如果是这样的话,我应该如何释放这段记忆?

注意:我能想到的一个答案是重新启动我正在执行程序的机器。 但是,如果我在远程计算机上执行程序并且重启不是一个选项?

简短回答:一旦您的进程终止,任何合理的操作系统都将释放该进程分配的所有内存。 所以不,当您多次重新启动进程时,内存分配不会累积。


进程和内存管理通常是操作系统的责任,因此在进程终止后是否释放已分配的内存实际上取决于操作系统。 不同的操作系统可以不同地处理内存管理

话虽这么说,任何合理的操作系统(尤其是多任务操作系统)都将释放进程终止后进程分配的所有内存。

我假设这背后的原因是操作系统必须能够优雅地处理不规则的情况:

  • 恶意程序(例如那些没有故意释放内存的程序,希望影响它们运行的​​系统)
  • exception程序终止(即程序意外结束的情况,因此可能无法明确free其动态分配的内存本身)

任何值得盐的操作系统都必须能够处理这种情况。 它必须将系统的其他部分(例如自身和其他运行过程)与故障过程隔离开来。 如果没有,进程的内存泄漏将传播到系统。 这意味着操作系统会泄漏内存(通常被认为是一个错误)。

保护系统免受内存泄漏的一种方法是确保一旦进程结束,就可以释放它使用的所有内存(以及可能的其他资源)。

无论是静态还是动态分配,程序终止时都应释放分配给程序的任何内存。 对此的主要例外是该进程是否分叉到另一个进程。

如果您没有显式free malloc任何内存,它将保持分配,直到进程终止。

即使您的操作系统在exit()上进行了清理。 要退出的系统调用通常由exit()函数包装。 这是一些伪代码,源自研究几个libc实现,以演示main()周围可能导致问题的原因。

 //unfortunately gcc has no builtin for stack pointer, so we use assembly #ifdef __x86_64__ #define STACK_POINTER "rsp" #elif defined __i386__ #define STACK_POINTER "esp" #elif defined __aarch64__ #define STACK_POINTER "x13" #elif defined __arm__ #define STACK_POINTER "r13" #else #define STACK_POINTER "sp" //most commonly used name on other arches #endif char **environ; void exit(int); int main(int,char**,char**); _Noreturn void _start(void){ register long *sp __asm__( STACK_POINTER ); //if you don't use argc, argv or envp/environ, just remove them long argc = *sp; char **argv = (char **)(sp + 1); environ = (char **)(sp + argc + 1); //init routines for threads, dynamic linker, etc... go here exit(main((int)argc, argv, environ)); __builtin_unreachable(); //or for(;;); to shut up compiler warnings } 

请注意,使用main的返回值调用exit。 在没有动态链接器或线程的静态构建中, exit()可以是直接内联的syscall(__NR_exit,main(...)) ; 但是如果你的libc使用exit()的包装器来执行*_fini()例程(大多数libc实现都这样做),那么在main()终止后仍然有一个函数要调用。

恶意程序可以LD_PRELOAD exit()或它调用的任何例程,并将其转换为一种永远不会释放其内存的僵尸进程。

即使你在exit() free()之前执行free() ,进程仍然会消耗一些内存(基本上是可执行文件的大小,在某种程度上是其他进程未使用的共享库),但是某些操作系统可以重新使用- 使用非malloc() ed内存用于同一程序的后续加载,这样你就可以在没有注意到僵尸的情况下运行数月。

FWIW,大多数libc实现都有某种exit()包装器,除了dietlibc(当构建为静态库时)和我只在Puppy Linux论坛上发布的部分静态libc.h。

正如我们所说的操作系统的大脑是内核 。 操作系统有几个职责。

内存管理是内核的一个function。

内核具有对系统内存的完全访问权限,并且必须允许进程根据需要安全地访问此内存。

通常,执行此操作的第一步是虚拟寻址,通常通过分页和/或分段来实现。 虚拟寻址允许内核使给定的物理地址看起来是另一个地址,即虚拟地址。 对于不同的进程,虚拟地址空间可能不同; 一个进程在特定(虚拟)地址访问的内存可能与另一个进程在同一地址访问的内存不同。

这允许每个程序的行为就好像它是唯一一个(除内核之外)运行,从而防止应用程序相互崩溃


内存分配

的malloc

堆中分配内存块

.NET等效项:不适用。 要调用标准C函数,请使用 PInvoke


堆是计算机内存的一个区域,不会自动为您管理,并且不受CPU的严格管理。 它是一个更自由浮动的内存区域(并且更大)。 要在堆上分配内存,必须使用malloc()calloc() ,它们是内置的C函数。 一旦你在堆上分配了内存,你就有责任使用free()在你不再需要它时解除分配该内存。 如果您不这样做,您的程序将具有所谓的内存泄漏 。 也就是说,堆上的内存仍然会被搁置(并且不会被其他进程使用)。


内存泄漏

对于Windows

当进程从分页或非分页池分配内存但未释放内存时,会发生内存泄漏。 结果,这些有限的内存池随着时间的推移而耗尽,导致Windows速度变慢。 如果内存完全耗尽,可能会导致故障。

  • 确定泄漏是否存在描述您可以使用的技术,如果您不确定系统是否存在内存泄漏。

  • 查找内核模式内存泄漏描述了如何查找由内核模式驱动程序或组件引起的泄漏。

  • 查找用户模式内存泄漏描述了如何查找由用户模式驱动程序或应用程序引起的泄漏。

防止Windows应用程序中的内存泄漏

内存泄漏是一类错误,应用程序在不再需要时无法释放内存。 随着时间的推移,内存泄漏会影响特定应用程序和操作系统的性能。 由于过度分页,大量泄漏可能导致不可接受的响应时间。 最终,应用程序以及操作系统的其他部分将出现故障。

Windows将释放应用程序在进程终止时分配的所有内存,因此短时间运行的应用程序不会显着影响整体系统性能。 但是,长时间运行的进程(如服务甚至Explorer插件)中的泄漏会极大地影响系统可靠性,并可能会强制用户重新启动Windows以使系统再次可用。

应用程序可以通过多种方式代表他们分配内存。 如果在使用后未释放,则每种类型的分配都可能导致泄漏

。 以下是常见分配模式的一些示例:

  • 堆内存通过HeapAlloc函数或其C / C ++运行时等效mallocnew

  • 通过VirtualAlloc函数从操作系统直接分配。

  • 通过Kernel32 API(如CreateFileCreateEventCreateThread创建的内核句柄代表应用程序保存内核内存

  • 通过User32和Gdi32 API创建的GDI和USER句柄(默认情况下,每个进程的配额为10,000个句柄)

对于Linux

memprof是一个用于分析内存使用情况和查找内存泄漏的工具。 它可以生成一个配置文件,程序中的每个函数分配了多少内存。 此外,它还可以扫描内存并查找已分配但不再在任何位置引用的块。

如果我使用malloc()在我的程序中动态分配内存但是我没有在程序运行时释放内存,那么在程序终止后是否会释放动态分配的内存?

操作系统将释放通过malloc分配的内存,以供其他系统使用。

这比你的问题听起来要复杂得多,因为进程使用的物理内存可能被写入磁盘(分页)。 但是,无论是Windows,Unix(Linux,MAC OS X,iOS,android),系统都会释放它为此过程提供的资源。

或者如果它没有被释放,并且我一遍又一遍地执行相同的程序,它每次都会分配不同的内存块吗? 如果是这样的话,我应该如何释放这段记忆?

每次启动该程序,都会获得一组新的内存。 这取自系统,并作为虚拟地址提供。 现代操作系统使用地址空间布局随机化(ASLR)作为安全function,这意味着堆应该在每次程序启动时提供唯一的地址。 但是,由于来自其他运行的资源已被整理,因此无需释放该内存。

正如您所指出的,如果后续运行无法跟踪其已提交资源的位置,那么它们如何能够释放它们。

另请注意,您可以在同一时间运行程序多次启动。 分配的内存可能看起来重叠 – 每个程序可能看到分配的相同地址,但这是“虚拟内存” – 操作系统已独立设置每个进程,因此它似乎使用相同的内存,但与每个进程关联的RAM会是独立的。

在程序执行时不释放程序的内存将在Windows和Unix上“工作”,可能还有任何其他合理的操作系统。

不释放记忆的好处

操作系统保留分配给进程的大内存块列表,并且malloc库还保留分配给malloc的小块内存表。

通过不释放内存,您将在进程终止时保存这些小列表的工作记帐。 在某些情况下甚至建议这样做(例如MSDN:服务控制处理程序建议应该通过NOT释放内存来处理SERVICE_CONTROL_SHUTDOWN)

不释放内存的缺点

诸如valgrind和应用程序validation程序之类的程序通过监视分配给进程的内存并报告泄漏来检查程序的正确性。

当你不释放内存时,这些会报告很多噪音,很难发现无意的泄漏。 如果你在循环内部泄漏内存,这将是很重要的,这将限制程序可以提供的任务的大小。

在我的职业生涯中,我已经多次将进程转换为共享对象/ dll。 这些都是有问题的转换,因为预计由操作系统进程终止处理的泄漏,开始存活超过“主要”的生命。

由malloc分配的内存需要由分配程序释放。如果没有并且内存保持分配,那么程序将耗尽允许的内存分配并抛出分段或内存不足错误。 malloc的每一组内存分配都需要免费提供。