在结构上的一系列新/删除之后,我似乎有记忆丧失

时间:2010-10-30 18:27:19

标签: c++

我有一个结构的链表,每个结构包含一个整数和一个指向下一个结构的指针。在通过一系列新命令填充此结构之前,我在Windows任务管理器的“内存使用情况”下记下此程序使用的内存(例如Mem_1)。接下来发生实际的链表创建。 (参见下面的void populate(int i)函数)。然后,我使用一系列删除尝试删除此链接列表,并希望回收内存。删除后,我在任务管理器中再次检查内存。这次使用的内存量是Mem_2。我注意到Mem_2> Mem_1。不应该Mem_2 = Mem_1?或者是否有一些悬垂的指针,我没有妥善处理。

感谢您的帮助......(该代码是控制台应用程序/ VS2008 / Windows XP平台)

struct item_s{
int value;
item_s* next;
};

struct item_s* item = NULL;
struct item_s* last_item = NULL;
struct item_s* last_accessed = NULL;

void populate(int i){
     if(item == NULL){
    item = new item_s;
    item->value = i;
    item->next = NULL;
    last_item = item;
}
else{
    last_item->next = new item_s;
    last_item->next->value = i;
    last_item->next->next = NULL;
    last_item = last_item->next;
}
}

void main(){
for(i = 1; i <= 10000; i++){
    populate(i);
}
last_item = item;
last_accessed = last_item->next;
while(last_item!=NULL){
    delete last_item;
    last_item = last_accessed;
    if(last_item!=NULL){
        last_accessed = last_item->next;
    }
}
}

3 个答案:

答案 0 :(得分:5)

两个这里有三大问题:

  1. 你为什么要new struct?您可能应该使用某种形式或RAII容器。如果你是,你根本不必担心内存泄漏(除非容器中有错误)当然包括std::vectorstd::auto_ptr。如果您有权访问C ++ 0x,那么还有std::unique_ptrstd::shared_ptr。如果您只能访问TR1(或增强库),则可用的类似容器为std::tr1::shared_ptrstd::tr1::scoped_ptr。 (在增强版中的boost::命名空间中)
  2. Windows'任务管理器报告进程占用的虚拟内存。 delete调用仅将内存返回给C运行时。 C运行时并不总是立即将内存返回给操作系统,因为操作系统分配(通过VirtualAlloc)非常昂贵。
  3. 这是int main()
  4. 要确定是否实际分配了内存,您需要该工具在C运行时级别运行,而不是在操作系统级别运行。

答案 1 :(得分:2)

这不是Windows内存管理器的工作原理。在分配和映射虚拟内存页面的麻烦之后,只是因为你停止使用它们而放弃了它们。那将是非常低效的。它只是保留它们,将释放的内存块添加到一个空闲块列表中。准备好在程序继续运行时再次使用。

Taskmgr.exe不足以对内存管理的工作方式进行逆向工程。特别是“Mem Usage”栏目。这只会告诉您程序当前使用了多少RAM。数字变化很大。最小化应用程序的主窗口,例如看到一个剧烈的变化。

Windows Internals是一本不错的书,可以了解它的工作原理。

答案 2 :(得分:0)

遗憾的是,您所看到的数字并未反映出您的想法:您的程序使用new调用分配的{strong> net 内存量{{1}无法匹配可能小于操作系统(OS)认为您的进程拥有的内容(这是您在此处查看的数字)。

这是因为当您调用delete时,您和操作系统之间会有一个称为堆管理器的层。当您使用new请求内存块时,堆管理器会在其OS内存的高速缓存中查找适当的连续空闲字节块,并且只有在没有内存时才会请求另一个OS块。这导致您正在观看的数量不断增加。

当您调用new时,堆管理器并不总是将该内存释放回操作系统(这将是昂贵的,并导致进程之间的过度争用)。通常,它只是将已发布的块添加回内部空闲列表,以便以后在程序的其他位置重用。随着时间的推移,从自由列表到使用和返回的这种输入和输出过程可能导致连续的空闲字节块变得难以找到。因此,堆管理器通常会执行压缩操作以将可用空间合并为更大的连续块,然后可以在需要时更有效地进行分区(以满足更多delete次调用)。

要在此处跟踪您的实际内存使用情况,您可以在程序结束时添加newnew个调用的全局计数器并输出它们是否匹配。

更好的方法是将“活动”对象的计数器封装为delete的静态成员,并在构造和复制构造时递增它,并在销毁时减少它。这样你总能知道你有多少个活动实例。如果程序中有多个线程,则需要使用++维护这样的计数器,并且 - 以线程安全的方式 - 大多数操作系统都有原子方法来执行此操作,例如。 Windows上的structInterlockedDecrement