tags:

views:

79

answers:

3

Hello All:

I have a linked list of structs each of which contains an integer and a pointer to the next struct. Before populating this struct by a sequence of new commands, I note down the memory used (say Mem_1) by this program in Windows Task Manager under "Mem Usage". The actual linked list creation occurs next. (See void populate(int i) function below). Then, I use a sequence of deletes to try and delete this linked list and hopefully reclaim the memory. After the deletes, I check the memory again in the Task Manager. This time the amount of memory used is, say, Mem_2. I notice that Mem_2 > Mem_1. Should not Mem_2 = Mem_1? Or is there some dangling pointer I am not properly taking care of.

Thanks for your help in advance...(The code is a console application/VS2008/Windows XP platform)

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;
    }
}
}
+5  A: 

TwoThree big problems here:

  1. Why are you newing structs anyway? You should probably use some form or RAII container instead. If you were, you wouldn't have to worry about memory leaks at all (unless there were bugs in the container of course) Examples include std::vector and std::auto_ptr. If you have access to C++0x there is also std::unique_ptr and std::shared_ptr. If you have access to TR1 only (or the boost libraries), similar containers available are std::tr1::shared_ptr, and std::tr1::scoped_ptr. (Which are in the boost:: namespace in the boost incarnations)
  2. Windows' task manager reports virtual memory consumed by the process. The delete call merely returns the memory to the C runtime. The C runtime does not always immediately return the memory back to the operating system, because OS allocations (via VirtualAlloc) are extremely expensive.
  3. It's int main() !

To determine whether memory is actually allocated, you would need that tool to operate at C runtime level, not at operating system level.

Billy ONeal
+1 as point 2 is an answer and point 3 is valuable. But point 1? Doesn't the code scream "I'm learning stuff and here are my experiments with a linked list" ? Sure, since it's supposed to be in C++, it would look much better, if that list was a class, not a bunch of global variables, but isn't sense of such experiments obvious. Let me guess. You like to ask questions, that verify the ability to manipulate list nodes in an interview and you like when people fail.
Maciej Hehl
@Maciej: Too often I see people "learning" C++ and they write code like this because they come from C# and Java land, where such code is not a problem. However, "idiomatic" Java or C# is horrendously bad C++ code. I always put things like this in here, even for beginners, because using `new` on structs is indicative of a misunderstanding of C++ object lifetimes. I wish someone had told me this before I went around writing garbage code for several years.
Billy ONeal
As for your last sentence, I have no idea about what you're talking about. I'm nowhere near in a position where I would ever be doing interviews, but if you were applying for a job working in C++ and did not know how to manipulate linked lists, you would not be getting a job with me. That's all covered in first year CS courses and is something that someone applying for a programming job should (at least conceptually) understand. (Of course, being able to rattle off perfect code on a whiteboard would not be required)
Billy ONeal
Last sentence was a joke or at least a half-joke. An attempt to guess, why you would discourage people from learning stuff that is essential (looks like we agree on that), by doing.
Maciej Hehl
@Billy - I think you are correct in identifying this as probably a new C++ developer's code. However, understanding the process this q covers using a brute-force example before moving to RAII is to me a useful part of the learning experience. [OP **has** come to the right place for accelerated learning, yes?] I think that somebody who has written and understood the above code is better placed to progress to RAII than a new developer who goes straight to RAII without understanding what's going on behind the scenes.
Steve Townsend
@Billy (cont.) In short - your points are valid, but I think they could be put differently in this context to get the point across. +1, after all that.
Steve Townsend
+2  A: 

This is just not how the Windows memory manager works. After it went to the trouble of allocating and mapping virtual memory pages, it does not give up on them just because you stopped using them. That would be very inefficient. It just keeps a hold of them, adding the freed blocks of memory to a list of free blocks. Ready to be reused again when your program continues running.

Taskmgr.exe is quite insufficient to reverse-engineer how memory management works. Especially the "Mem Usage" column. Which only tells you how much RAM your program is currently using. A highly variable number. Minimize your app's main window for example to see a drastic change.

Windows Internals is a decent book to learn more about how this works.

Hans Passant
Yes, I guessed that "Mem Usage" might be a bad proxy...but here is my problem...the code in the original post is repeatedly solved nearly thousands of time...and at the end of it, it crashed. At that point, I checked the "Mem Usage" column (granted it is not what I should be looking at) and it was nearly half of the total RAM on my machine...If I am using new/delete and matching each new with a correct delete, the above code should NOT crash even if it is run a million times, n'est ce pas? Thanks.
Tryer
Hmm, that's not what your question said at all. Well, fairly convincing evidence that you are leaking and/or corrupting the heap. At the risk of kicking in an opened door, you'll need to debug your code. Ask a targeted question if you need help with this.
Hans Passant
A: 

The number you are looking at unfortunately does not reflect what you think it does: the net amount of memory that your program has allocated using new calls that are unmatched by delete is probably less than what the operating system (OS) thinks your process owns (that's the number you are viewing here).

This is because when you call new there is a layer called the heap manager between you and the OS. When you ask for a block of memory using new, the heap manager looks for an appropriate contiguous block of free bytes in its cache of OS memory, and only if there is none does it request another OS block. This results in the number you are watching increasing.

When you call delete, the heap manager does not always release that memory back to the OS (that would be expensive, and cause excessive contention between processes). Most often, it just adds the released block back to an internal free list for later reuse elsewhere in your program. Over time, this in-and-out process from free list to in use and back can result in contiguous blocks of free bytes becoming hard to find. Every so often therefore, the heap manager will perform a compaction operation to coalesce free space into larger contiguous blocks, which can then be partitioned more efficiently when the need arises (to satisfy more new calls).

To track your actual memory usage here, you could add global counters of new and delete calls and output at the end of your program whether they match up.

A better way to do this would be to encapsulate the counter of 'active' objects as a static member of your struct, and increment it on construction and copy construction, and decrement it on destruction. That way you always can tell just how many active instances you have. If you have multiple threads in your program, such a counter would need to be maintained using ++ and -- in a threadsafe way - most OSes have atomic ways to do this eg. InterlockedDecrement and InterlockedIncrement on Windows.

Steve Townsend
Thx Steve...I do think if I had the static member in my struct, it would read 0 at the end of it all...It is just that there is some memory leak somewhere...because on repeatedly calling this about a 1000 times, the memory used by this app explodes crashing my comp...Even though, as others have pointed out, this code is not the most efficient/elegant I am deleting every new struct...But I may be leaving some pointers dangling, I guess.
Tryer
If you add an explicit constructor and destructor to your struct (class ?) and have them output something every time they are called `std::cout << "cnstr" << std::endl;`, `std::cout << "destr" << std::endl;`, say - you would quickly know if the calls are correctly matched up without looping 1000 times.
Steve Townsend