Thursday, April 13, 2006

Why doesn't a variable change when assigned a function result?

Consider this:
function someIntFunction: integer;
begin
  if false then
    result := 20;
end;

procedure someIntMethod;
var
  lValue: integer;
begin
  lValue := 10;
  lValue := someIntFunction;
  ShowMessage(IntToStr(lValue));
end;
What does ShowMessage show?

When this code is compiled, a compiler warning is issued for someIntFunction indicating that the result may be undefined. This is a valid warning and what is displayed will be whatever happens to be on the stack; some random value.

Now consider this:

function someStringFunction: string;
begin
  if false then
    result := '20';
end;

procedure someStringMethod;
var
  lValue: string;
begin
  lValue := '10';
  lValue := someStringFunction;
  ShowMessage(lValue);
end;
When this is compiled, there is no warning for someStringFunction. A warning should probably also be emitted, but it's not. The effect however is a bit different. In this case, the variable that the result is assigned to is unchanged. This seems to occur for any type that is reference counted: strings, dynamic arrays and interfaces.

Monday, January 2, 2006

What is the difference between Parent and Owner?

Parent is the Window control where the control is displayed. Owner is the component responsible for making sure it's destroyed.

When a component is created, an owner (which is another component) is specified as a parameter to the Create method. Unless the component is destroyed earlier, the owner will automatically free the component when the owner is destroyed. This is the purpose of ownership.

Controls that appear on the screen need to know where to paint themselves, hence the parent property. It specifies the visual container for the control. This property must be specified before a control can be displayed.

When a component is dropped on a form at design time, the owner is the form and the parent is the container on which it is dropped. For example, drop a group box on a form. Its owner and parent are both the form. Drop a check box on the group box. Its owner is the form and its parent is the group box.

The specification of owner and parent are at the programmer's discretion when controls are created dynamically and so the above relationships may not hold. This is especially true with composite components. With run-time creation of controls, the programmer may specify any component to be an owner (including a nil owner) and any window control as the parent. If a nil owner is specified, the programmer is responsible for explicitly freeing the object.

Why are some object properties zero in the watch window?

This has to do with how the watch window works and the way properties are implemented.

First, the watch window simply provides a view of an area of memory. You can tell it to view the memory in different ways. Because it knows some things about that memory, it can do a limited amount of interpretation: view the contents of character pointers, view the contents of records and so forth.

Second, a bit of explanation as to how properties work. When a component is developed, the component writer declares a property with:
property NameOfProp: TypeOfProp read PropReader write PropWriter;
where:
NameOfProp is the name of the property accessed in the Object Inspector and in code.
TypeOfProp is the type of the property, e.g. Integer, String, TStrings.
PropReader is either a variable of type TypeOfProp or a function with a return type of TypeOfProp.
PropWriter is either a variable of type TypeOfProp or a procedure with a parameter of type TypeOfProp.
Notice the two options for readers and writers. They can be either a variable or a method. Some properties directly access a variable whereas others are the result of code executing. Those properties that fall into the first class can be evaluated without side effects in the watch window, since it looks at memory. Those properties that fall into the second class cannot be evaluated in the watch window without potentially causing side effects.

Because of the possible side effect issue, early versions of Delphi simply did not display the property value. Due to requests to allow this, the debugger was made to optionally show properties which use readers. To see these types of properties, right click on the watch item and select properties. Then check the option to allow function results.

If working in earlier versions of Delphi, where this option is not available, there are several ways to get around the problem:
  1. Create a temporary variable immediately prior to where you want to view a property and assign the property value to the variable. Then put the watch on the temporary variable. You may need to go to the Options page and turn optimizations off.
  2. Put a ShowMessage call in the code where you would normally set the breakpoint and create a message that displays the desired property.
  3. Put a Memo component on the visible form and add a line that displays the desired property where you would normally set the breakpoint.
  4. Set a breakpoint. Right click on the breakpoint in the breakpoint list window and select properties. Show the advanced options. Uncheck Break. In the evaluation section, put in the property name. Now, every time that breakpoint is hit, rather than stopping the debugger, it will write a line to the event log.

Why do I get an AV when accessing an array?

The most common AV problem with arrays is accessing an element beyond the proper range. Make sure range checking is on, at least for debugging.

Why do I get an AV when accessing a pointer (including a PChar) or object?

In the following discussion, the term pointer is used to generically refer to variables of explicit pointer types, PChars and objects. This was written from a Delphi perspective, but the techniques are generally applicable.

Remember, declaring a variable of any pointer type only allocates 4 bytes for the pointer. The pointer does not refer to anything. Before it can be referenced, it must be assigned a value. Depending on context, the pointer's value may come from any number of sources, including: memory allocation routines, a return value from a function, a constant, a variable or, in the case of objects, the constructor method. Not performing this step is a sure way to AV. Always check the return value of memory allocation functions to verify that the pointer was assigned a valid value.

Another way to AV sometimes is to access a pointer whose contents have already been freed. Depending on many factors, sometimes this will work and sometimes it will AV. This is especially easy to do if the variable's scope is greater than the current procedure. One good practice, particularly when variables have an object or global scope is to set their value to nil immediately after freeing it. No, this is not automatically done. If nothing else, following this practice will eliminate the "sometimes" nature of the problem.

Assuming the pointer has been assigned a valid value and it has not been freed, the final item to check is the value of the pointer when the AV occurs. If it is the same value, then the memory is being referenced (possibly through a typecast) incorrectly. Accessing a freed object can cause this too. If it is a different value, then the next task is to track down why. Probably something else in the program is accessing memory incorrectly, not causing an AV but corrupting this pointer. On cause of this is writing to array elements outside the bounds of the array. Turn on the range checking option to help determine if this is happening.

There are several ways to find this. 1) Do a very careful code review until the offending code is found. 2) Establish breakpoints between the time the value is correct and when it is bad with a watch on the pointer that's being changed. 3) Remove code between creation and known corruption until the problem goes away. 4) Use a tool that detects memory overwrites.

What are general debugging techniques for AVs?

When you receive the dreaded Access Violation, the first thing to understand is what the computer is actually telling you. An AV is the Windows' description of an exception that was raised by the hardware itself. The hardware knows which processes should be accessing what memory and has detected a rogue process trying to read or write to memory that it does not own. It then tells the operating system which handles the error in various ways depending on the O/S and the context of the error.

So when you see the error with those cryptic long hex addresses, what the computer is telling you is that the instruction at the address of the named program tried to reference memory that it did not have rights to. That is all it means. It does not mean that the code at that address is necessarily the culprit, although it may. It does not mean that the named program is faulty, although it may. It simply means the hardware determined that the named program at that moment in time was not operating properly.

Another thing to keep in mind is that just because you do not have AVs does not mean you do not have a problem with memory access. It just means the hardware has not detected it. You may very well have problems but they do not extend past memory you do have rights to so the hardware does not detect it. Symptoms of this are when variables change value for no apparent reason and "correctly" running code suddenly starts AVing for no apparent reason.

Remember too, different operating systems map memory differently. There may be a problem on one platform that is not manifest on others. Many times people develop a program in Windows X and have no problems. They then run it in Windows Y, get AVs and blame the O/S. It cannot be their program since it runs just fine on the other system. No. They just have not found the problem on the other system. This is actually not unique to Pascal programs or the Windows O/S. For example, the same principle holds true for C programs in UNIX. Many times code that has run flawlessly for years on one version falls over when it is compiled on another platform.

Because of the inexact nature of AV reporting, they can be very trying to track down. Within the Delphi environment there are a couple common bugs to look for:
  1. Is it during object creation?
  2. Is it during object destruction?
  3. Is it when accessing a pointer (including a PChar) or object?
  4. Is it when accessing an array?

Why do I get AVs when accessing a TStrings' Objects property?

Typically, this is because a corresponding string has not been assigned.

TStringLists use what is called sparse memory management. This means that memory (including the memory for the object's pointer) is assigned to each element only when something is placed in the string. Therefore, when an object is referenced without the string, no memory is allocated but it is trying to be accessed anyway, hence the AV.

There are a couple solutions: 1) use the AddObject method, 2) assign a value to the corresponding string first.

Note: Objects stored in TStrings descendants do not get freed when the TStrings object is destroyed. The lifetime of anything you place in here must be explicitly maintained.
procedure example1;
begin
with TStringList.Create do
try
Objects[0] := TObject.Create; { This will AV }
finally
Objects[0].Free;
Free;
end;
end;

procedure example2;
begin
with TStringList.Create do
try
AddObject('This is one good way.', TObject.Create);
Add('This is another good way.');
Objects[1] := TObject.Create;
finally
Objects[0].Free;
Objects[1].Free;
Free;
end;
end;