Skip to content

Bluff the Programmer

I enjoy listening to National Public Radio's Wait Wait Don't Tell Me" on saturday mornings. "Wait Wait" is a show in which listeners call in and are quizzed (in very humorous fashion) by normally respectable newscasters and radio personalities, and if they get their quizzes right, the big prize is Carl Kassel's voice on your home answering machine.

One of the most entertaining segments of the program is one they call "Bluff the Listener," where three of the personalities tell a story, supposedly from the national news of the prior week. Only one of the stories is true, and the goal is to choose which of the three is real.

This week, we're going to play "Bluff the Programmer." I'm going to give you three snippets of code. Only one of them is, in fact, valid Delphi code. You pick the right one.

Oh, and actually testing the code is highly discouraged, as that would make it too easy. :)

Option #1:

  1. Memo1.Lines.Add(Format('1: %s' + ^M + '2: %s', [Edit1.Text, Edit2.Text]));

Option #2:

  1. property Publisher: string read 'TwoDesk Software Company';

Option #3:

  1. procedure Button1Click(Sender: TObject);
  2. begin
  3.   case Sender of
  4.     Button1: Form1.Show;
  5.     Button2: Form2.Show;
  6.   end;
  7. end;

That's it. Two of those are illegal. One works. Which is which?

(Edited about 9 hours after posting for a couple of typos)

Solution to “Short and… what?”

In Windows, resource names have to be in all caps. 'Bitmap1' is not a legal resource name, but 'BITMAP1' is.

However, that's not the complete answer, because this code will work - the API for dealing with resource will take the uncapitalized names and work with them as if they were capitalized. But if the name of the resource as saved in the executable (or in the .res file that gets compiled into the executable) is not in all caps, the resource will never be found, and an exception will be raised.

Further considerations:

Older versions of Delphi came with the Image Editor that would edit the image data in .res files. Whenever you would rename a resource, it would save the resource name in all caps, no matter what you typed. BUT, when you clicked "New Bitmap" to create a new bitmap resource, it would create the bitmap with the resource name 'Bitmap1' (or whatever number was next), and would NOT translate that into uppercase letters. Oops!

Review:

1. Resources must be stored with all-uppercase names.

2. Make sure you rename resources in the image editor, because the default names are broken.

Short and… what?

Last week's puzzle was lengthy and took some thinking to figure out. This week's is simple, and if you know it, you'll know it right away.

If you saw the following code, and were told it wasn't working right, what would you first suspect the problem is?

  1. Image1.Picture.Bitmap.LoadFromResourceName(HINSTANCE, 'Bitmap1');

(Edited about 12 hours after posting for a typo)

Solution to “I have to watch my soaps”

This one was actually based on code I wrote, and a bug I created. It wasn't in Delphi though, but the logic error I created was interesting enough to make a note and turn it into a puzzle. When I translated the buggy code into Delphi, it behaved exactly the way I expected. Then I translated the fix, and it did NOT behave the way I expected. That is, there was another bug.

...and that's what you got for this puzzle.

The problem here is one of assumption. That assumption is that if you don't assign any value to ShowRec.Length, then it will be zero. Our programmer may have assumed this because he thought the compiler would produce a zeroed-out array, or perhaps he assumed that the caller would zero out the array. In any case, that's not what happened, and his array was full of random numbers - leftover memory from another lifetime.

As we look at the code, the problem arises on line 28, when Times[I].Length is accessed. At first this seems ok, because we set Length back on line 18. But then if conditions allow us to get to line 31, we set ShowName, but not length. Length will be garbage. When we loop back and get to line 26, ShowName is set, so we pass the condition, and then get to line 28 where we access Length which is (!) garbage. It could be 0, it could be 1, it could be 4097234, it could even be $CAFEBABE. Whatever it is, if it's greater than 30, it's going result in a value for Blocks that will cause the J loop to execute many more times than we want it to.

When that happens, we'll see the array fill up with references to a single show. Instead of watching House for one hour, we'll be seeing it 17 hours a day. Come to think of it, that might not be such a bad thing....

The solution?

Our first instinct might be to add after line 31:

  1. Times[J].Length := Times[I].Length;

But that actually results in the same problem, just with more predictable but equally useless values for Length. In a nutshell, Length can ONLY be set for the first member of the array representing a show. It has to be zero for the subsequent members.

So, there's our answer:

  1. Times[J].Length := 0;

If you think about it, this makes sense. Our assumption was that the array would have zeros anywhere that we didn't assign a value. We know that's not true, so we need to put the zeros there ourselves.

BONUS QUESTION FOR THE WEEKEND:

If the compiler isn't putting zeros in the length field, it stands to reason that it's also not putting empty strings in the ShowName field where we don't assign it. Why didn't that cause an access violation or some other nasty?

I have to watch my soaps

Pedro is a graduate student at the university, and has been asked to help create a program to run the university television station. Pedro's job is to write Delphi code to make data in the database useable for both a desktop application and a web application.

Today, Pedro has been tasked with writing a routine that will get the daily schedule of shows for the television station. This routine will be used to generate a web page that will show a schedule for any day. The web guy wants an array of 48 elements (one for each half hour in the day) that he can iterate to build a table. The array should be an array of TShowRec:

  1. TShowRec = record
  2.   ShowName: string; //Title of the show
  3.   Length: Integer; //Length of the show in minutes.  Must be a multiple of 30
  4. end;
  5.  
  6. TTimeArray = array[0..47] of TShowRec;

Pedro's strategy is to query the database for all the shows that air on a given day, then put each show in the array according to its time. After that, he'll run through the array and set additional elements for any show longer than 30 minutes (so that an hour-long show uses two elements).

Pedro wrote and tested his SQL, and was satisfied that it worked properly. Then he wrote his Delphi code:

  1. procedure TScheduler.GetScheduleArray(Date: TDateTime; var Times: TTimeArray);
  2. var
  3.   ShowTime: TDateTime;
  4.   Idx, I, J, Blocks: Integer;
  5. begin
  6.   ScheduleQuery.ParamByName('DAYOFWEEK').AsInteger := DayOfWeek(Date) - 1;//Database uses 0 for sunday
  7.  
  8.   //Get the show times into the array
  9.   ScheduleQuery.Open;
  10.   ScheduleQuery.First;
  11.   while not ScheduleQuery.EOF do
  12.   begin
  13.     ShowTime := ScheduleQuery.FieldByName('airtime').AsDateTime;
  14.     Idx := HourOf(ShowTime) * 2;
  15.     if MinuteOf(ShowTime) = 30 then
  16.       Inc(Idx, 1);
  17.     Times[Idx].ShowName := ScheduleQuery.FieldByName('title').AsString;
  18.     Times[Idx].Length := ScheduleQuery.FieldByName('length').AsInteger;
  19.     ScheduleQuery.Next;
  20.   end;
  21.   ScheduleQuery.Close;
  22.  
  23.   //now go back and fix lengths
  24.   for I := 0 to 47 do
  25.   begin
  26.     if Times[I].ShowName <> '' then
  27.     begin
  28.       Blocks := (Times[I].Length div 30) - 1;
  29.       for J := I + 1 to Min(I + Blocks, 47) do
  30.       begin
  31.         Times[J].ShowName := Times[I].ShowName;
  32.       end;
  33.     end;
  34.   end;
  35. end;

When Pedro tested his routine against the live database, he was very surprised by the results. What did he see, and what is the best way to fix it?

Solution to Passing Grades

Peter has missed the double meaning of the NOT operator. He wants to see all of the students that do not have a grade over 60, so he uses this line to see if the grade is where he wants it:

  1. if not AllStudents[I].CalculateGrade> 60 then
  2.   FailingList.Add(AllStudents[I]);

What Peter doesn't realize is that in this case, the NOT operator isn't going to tell him if his expression is not true, but rather is going to return the bitwise NOT of CalculateGrade. If the grade is 40, the bitwise NOT will be -41, (assuming that the grade is expressed as an integer). A student with a grade of 40 will still be included in the list.

BUT, the bitwise NOT of 70 is -71, so a student with a passing grade of 70 will also be included in the list of failing students, and Peter's program receives a failing grade.

Once Peter realized this, he changed his code:

  1. if not (AllStudents[I].CalculateGrade> 60) then
  2.   FailingList.Add(AllStudents[I]);

A few weeks later, Peter was looking at the same code again, and realized he could make it even more precise:

  1. if AllStudents[I].CalculateGrade <61 then
  2.   FailingList.Add(AllStudents[I]);

If only he'd done that in the first place, he never would have gotten confused.

Passing Grades

Peter, a school teacher, is writing an application in Delphi 7 to help him track the grades of his students. Today he is writing code that will give him a list of all the students in his class that are not passing. Peter defines a passing grade as a grade higher than 60%. Here's the code:

  1. procedure TMainForm.GetFailingStudents;
  2. var
  3.   I: Integer;
  4.   FailingList: TStudentList; //A TList descendant
  5. begin
  6.   FailingList := TStudentList.Create;
  7.   try
  8.     for I := 0 to AllStudents.Count - 1 do
  9.     begin
  10.       if not AllStudents[I].CalculateGrade> 60 then
  11.         FailingList.Add(AllStudents[I]);
  12.     end;
  13.     ShowStudentList(FailingList);
  14.   finally
  15.     FailingList.Free;
  16.   end;
  17. end;

Peter can't figure out why his code doesn't show him the list of students he expected to see. Can you help him?

Solution to A Little Housekeeping

Sarah's mistake was in the way she moved through the records in her table. She started with the basic pattern of iteration through a table:

  1. Table1.First;
  2. while not Table1.EOF do
  3. begin
  4.   {process record here}
  5.   Table1.Next;
  6. end;

Then she added code to check the record and delete it if its CreationDate was before January 1, 1998. The problem is that calling Table1.Delete deletes the current record AND moves ahead to the next record.

By calling Table1.Next right after calling Delete, Sarah created code that would skip over every record immediately after a record that needed to be deleted. If there are two records in a row that are older than 10 years , only the first would be deleted; the second would remain.

Sarah fixed the code by making her statement into an if..else:

  1. if Table1.FieldByName('CreationDate').AsDateTime &lt;
  2.     EncodeDate(1998, 1, 1) then
  3.   Table1.Delete
  4. else
  5.   Table1.Next;

A little housekeeping

Sarah's company has an old application that still uses a Paradox database. As the end of the year approaches, the boss has asked Sarah to write some one-time-only code to clean out records older than 10 years. Sarah sets to this fairly simple task and, having her database connection all set up, writes the following code:

  1. if not Table1.Active then
  2.   Table1.Open;
  3. Table1.First;
  4. while not Table1.EOF do
  5. begin
  6.   if Table1.FieldByName('CreationDate').AsDateTime <
  7.     EncodeDate(1998, 1, 1) then
  8.       Table1.Delete;
  9.   Table1.Next;
  10. end;
  11. Table1.Close;

Sarah ran her program and confirmed that a number of records were deleted, but every once in a while, someone would find a record more than ten years old. What did Sarah do wrong?

Solution to A Very Moving Message

When you try to compile this code, you get an error message that isn't much help. The compiler stops on the message handler declaration with the error "Invalid message parameter list."

What the compiler wants to see is the message parameter modified as a var parameter:

  1. procedure WMNCHITTEST(var Msg: TWMNCHITTEST); message WM_NCHITTEST;
  2.  
  3. {...and...}
  4.  
  5. procedure TMoveableForm.WMNCHITTEST(var Msg: TWMNCHITTEST);
  6. {etc...}