tags:

views:

828

answers:

6

In my Delphi7 this code

var MStr: TMemoryStream;
...
FreeAndNil(MStr);
MStr.Size:=0; 

generates an AV: Access violation at address 0041D6D1 in module 'Project1.exe'. Read of address 00000000. But somebody insists that it should not raise any exception, no matter what. He also says that his Delphi 5 indeed raises no exceptions. He calls this a “stale pointer bug”. In other words he says that FreeAndNil cannot be used as debugger to detect a double attempt to free an object or to use a freed object.

Can anybody enlighten me? Should this raise and error (always/randomly) or the program should run over this bug without problems?

Thanks


I ask this because I believe I have a "double free object" or "free and re-access" bug in my program. How can I fill the memory allocated to an object with zeros AFTER I freed the object? I want this way to detect where the bug is, by getting and AV. Initially, I hoped that if I set the object to FreeAndNil, I will ALWAYS get an AV when trying to re-access it.

+8  A: 

From what I am seeing, this code should always result in an error. FreeAndNil explicitly sets that passed value to Nil (aka 0), so you should absolutely get an access violation when trying to dereference the object.

Scott W
+19  A: 

It's always wrong to use methods or properties of a null reference, even if it appears to work sometimes.

FreeAndNil indeed cannot be used to detect double frees. It is safe to call FreeAndNil on an already-nil variable. Since it's safe, it doesn't help you detect anything.

This is not a stale-pointer bug. This is a null-reference bug. A stale-pointer bug is when you have freed an object but not cleared all variables that referenced it. Then the variable still holds the old address of the object. Those are very hard to detect. You can get such a bug like this:

MStr := TMemoryStream.Create;
MStr.Free;
MStr.Size := 0;

You can also get one like this:

MStr := TMemoryStream.Create;
OtherStr := MStr;
FreeAndNil(MStr);
OtherStr.Size := 0;

Using MStr.Size after you have freed the object MStr referenced is an error, and it should raise an exception. Whether it does raise an exception depends on the implementation. Maybe it will, and maybe it won't. It's not random, though.

If you're searching for a double-free bug, you can use the debugging aides that FastMM provides, as others have suggested as well. It works by not actually releasing the memory back to the operating system, or even back to Delphi's internal free-memory pool. Instead, it writes known-bad data into the object's memory space, so when you see those values, you'll know you're reading from something that you already freed. It also modifies the object's VMT so that the next time you call a virtual method on that object reference, you'll get a predictable exception, and it will even tell you which supposedly freed object you tried to use. When you attempt to free the object again, it can tell you not only that you already freed it, but also where it was freed the first time (with a stack trace), and where it was allocated. It also collects that information to report about memory leaks, where you freed an object less than one time instead of more.

There are also habits you can use to avoid the issue for future code:

  • Reduce the use of global variables. A global variable could be modified by any code throughout the program, forcing you to wonder whenever you use it, "Is this variable's value still valid, or did some other code free it already?" When you limit the scope of a variable, you reduce the amount of code you have to consider in your program when looking for reasons a variable doesn't have the value you expect.
  • Be clear about who owns an object. When there are two pieces of code that have access to the same object, you need to know which of those pieces of code owns the object. They might each have a different variable for referencing the object, but there's still just one object there. If one piece of code calls FreeAndNil on its variable, that still leave's the other code's variable unchanged. If that other code thinks it owns the object, then you're in trouble. (This concept of owner is not necessarily tied to the TComponent.Owner property. There doesn't need to be an object that owns it; it could be a general subsystem of your program.)
  • Don't keep persistent references to objects you don't own. If you don't keep long-lived references to an object, then you don't have to worry about whether those references are still valid. The only persistent reference should be in the code that owns the object. Any other code that needs to use that object should receive a reference as an input parameter, use the object, and then discard the reference when it returns its result.
Rob Kennedy
+5  A: 

If you set a pointer to nil, you shouldn't be able to use it any more. But if you have another pointer to the same object, you can use it without getting an AV, because this pointer still points to the object address and not to nil.

Moreover, freeing an object do not clear the memory used by the that object. It just marks it as not in use. Thats the reason you want get an AV. If the freed memory is allocated for another object, you will get a AV, because it no longer contains data that seems valid.

FastMM4 has some settings that you can use while debugging, that will detect such conditions. From the FsatMM4Options.inc:

{Set the following option to do extensive checking of all memory blocks. All blocks are padded with both a header and trailer that are used to verify the integrity of the heap. Freed blocks are also cleared to to ensure that they cannot be reused after being freed. This option slows down memory operations dramatically and should only be used to debug an application that is overwriting memory or reusing freed pointers. Setting this option automatically enables CheckHeapForCorruption and disables ASMVersion. Very important: If you enable this option your application will require the FastMM_FullDebugMode.dll library. If this library is not available you will get an error on startup.}
{$define FullDebugMode}

Another quote from the same file:

FastMM always catches attempts to free the same memory block twice...

As delphi uses FastMM from Delphi 2007 (2006 ?), you should get an error if you try to doublefree an object.

Vegar
Free and nil also frees the object so another pointer to it will also fail.
Gamecat
Is that true for older delphi versions and the classic memory manager, or just for Delphi 2007+ with FastMM? What would be the purpose for the comment in FastMM4Options.inc if what you say is the truth?
Vegar
Gamecat, it's very much possible that reusing a stale (non-nil) pointer will not raise any exception (and it's possible it might work just fine.) Of course, that's a bad thing, and it's a thousand times better if it fails, but at least in Delphi 5 and 7 you can reuse stale pointers.
Mihai Limbășan
It's true for all versions. The FastMM comment means that freed blocks cannot be used for *useful* purposes. You can still read what's in the memory, but it will be the known-bad values that FastMM wrote there instead of values that were in the object before.
Rob Kennedy
Moocha, you can dereference stale pointers in all versions. That doesn't mean you'll get the right values, though. With FastMM's debug features turned off, it may re-allocate freed memory for something else. Your stale pointer will get that data instead of the object's previous values.
Rob Kennedy
+8  A: 

Just to complicate the issue:

If the method you call is a static (not virtual) method and it does not call any virtual methods itself nor does it access any fields of the object, you will not get an access violation even if the object reference has been set to NIL.

The reason for this is that the access violation is caused by dereferencing the self pointer (in this case NIL), but that only happens when accessing a field or the object's VMT for calling a virtual method.

This is just an exception to the rule that you cannot call methods of an NIL object reference that I'd like to mention here.

+1  A: 

Thomas Mueller: have you tried virtual class methods? A constructor is sort of a virtual method but you call it against the type - not the instance. This means that even some specific virtual methods will not cause AV on a null-reference :D

Vegar: You couldn't be more right! FastMM is the best ever ever ever tool that helped me tracking down this kind of bugs.

Matthias Hryniszak
Calling a virtual class method against an instance variable will fail. Constructors have nothing to do with it.
Rob Kennedy
+1  A: 

The EurekaLog Blog had a great post on this in April 2009:

Why should you always use FreeAndNil instead of Free.

Mick