nrjohnstone
nrjohnstone

Reputation: 818

Delphi CompareMem with pointers

I've been developing some software in Delphi 5 using DUnit as a driver for TDD but I found that when using CheckEqualsMem it kept failing even though in the debugger I could see that the objects being compared (two arrays of longwords in this case) were identical.

Internally, CheckEqualsMem uses CompareMem and found that this was what was returning false.

Delving a bit deeper I found that if I call CompareMem with a pointer to the address of the objects using @ or Addr CompareMem fails even when the memory is identical, but if I use PByte (from windows) or PChar it will compare the memory properly.

Why?

Here is an example

var
  s1  : String;
  s2  : String;
begin
  s1 := 'test';
  s2 := 'tesx';

  // This correctly compares the first byte and does not return false 
  // since both strings have in their first position
  if CompareMem(PByte(s1), PByte(s2), 1) = False then
    Assert(False, 'Memory not equal');

  // This however fails ?? What I think I am doing is passing a pointer
  // to the address of the memory where the variable is and telling CompareMem
  // to compare the first byte, but I must be misunderstanding something
  if CompareMem(@s1,@s2,1) = False then
    Assert(False,'Memory not equal');

  // Using this syntax correctly fails when the objects are different in memory
  // in this case the 4th byte is not equal between the strings and CompareMem
  // now correctly fails
  if CompareMem(PByte(s1),PByte(s2),4) = False then
     Assert(False, 'Memory not equal');
end;

As you can see in the comments, I come from a C background, so I thought that @s1 is a pointer to the first byte of s1 and PByte(s1) should be the same thing, but its not.

What am I misunderstanding here? What is the difference between @ / Addr and PByte ??

Upvotes: 3

Views: 4866

Answers (2)

jachguate
jachguate

Reputation: 17203

the string type in Delphi is a managed type.

The string variable itself is a pointer to a structure in memory, which typically contains more than the pure character information, like the code page, reference count, etc.

You can find more information in the article Internal Data Formats in the DocWiki

The at operator gives you the memory where the variable resides. This is, in this case, the address where the pointer is stored. Not even the address where it points to. If s1 and s2 are local method variables, for example, @s1 returns the address in the stack where the variable lives.

If you want to compare the strings contents, better compare @s1[1] against @s2[1], since that points to the first character in each string.

If you want to compare if both strings points to the same string (different tan having the same content), you can cast s1 as a pointer and compare its values, or cast @s1 as a pointer to pointer.

I hope this example helps you to get more idea of what's going on:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

var
  s1  : String;
  s2  : String;

begin
  try
    s1 := 'test';
    //points to the same data
    s2 := s1;

    Assert(@s1<>@s2, 'They are at the same memory address, no way!');
    Assert(PPointer(@s1)^ = PPointer(@s2)^, 'They points to different address!');
    Assert(PPointer(@s1)^ = Pointer(s1), 'What jachguate says is a lie!');
    Assert(CompareMem(@s1[1], @s2[1], Length(s1) * SizeOf(Char)), 'Woa, differnet data');

    s2 := 'tesx';
    //points to different string, with partial equal content

    Assert(PPointer(@s1)^ <> PPointer(@s2)^, 'They still points to the same address!');
    Assert(CompareMem(@s1[1], @s2[1], 3 * SizeOf(Char)), 'Difference in the first 3 chars!');
    Assert(not CompareMem(@s1[1], @s2[1], 4 * SizeOf(Char)), 'No difference at all!');

    Writeln('All passed');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

But this is different from your real world problem

If you use dynamic arrays, you have to remember it is also a managed type, so you must better get the address of the first element of each array to compare.

If that's not the case, I advise you to use real code in your question to show your real problem and not a different one.

Upvotes: 4

kludg
kludg

Reputation: 27493

The difference is that @s1 is address of the variable s1 while PByte(s1) is value of the variable s1.

A string variable internally is a pointer to a string data, so @s1 is a pointer to pointer. The line

CompareMem(@s1,@s2,1);

compares two least significant bytes of the addresses of s1 and s2 strings; it makes no sense and have no relation to the string data referenced by Pointer(s1) and Pointer(s2).

Upvotes: 6

Related Questions