delphipointersdelphi-xe2ansistring

Delphi AnsiString Manipulation - PAnsiChar becomes corrupt?


I use the following approach to move through and manipulate an AnsiString. It works most of the time, but sometimes the pointer into the string quits functioning. Given the following code:

var
  s: AnsiString;
  p: PAnsiChar;
  offset, idx, cnt: Integer;
begin
  s := 'some>very>long>string>with>field>delimiters>';
  p := @s[1];
  offset := 1;

  // find the 5th field
  cnt := 5;
  repeat
    idx := AnsiString.AnsiPos('>', p);
    Inc(p, idx);
    Inc(offset, idx);
    Dec(cnt);
  until cnt = 0;

  // insert a new field after the 5th field
  Insert(AnsiString('something new>'), s, offset);

  // skip other fields
  // insert other values
  // repeat
end;

When debugging, just after the repeat..until loop finishes you can look at the inspector and see that p = 'field>delimiters>'. After the Insert() statement, s = 'some>very>long>string>with>something new>field>delimiters>' and p = 'something new>field>delimiters>' in the inspector. This is as expected.

My real string is several thousand characters long. This method of moving through the string and adding new fields works dozens of times, then it suddenly stop working. p no longer shows the inserted value at the front of the string after the call to Insert(). p doesn't seem to know that s has changed...

Why does p properly reference a character on s after most Insert() statement, and suddenly stop working after some calls to Insert()?

(I discovered the answer to my question while typing it up. The answer seems obvious now, but not so while I was struggling with the issue. Perhaps posting the question and the answer will help someone else...)


Solution

  • When you call Insert(), the memory manager will move the AnsiString to a new location in memory if there isn't enough additional contiguous memory to extend the buffer in its current memory location. This leaves p pointing to the old memory location, which does not hold the modified string, and will likely lead to access violations.

    Adding one line of code to reinitialize p after each Insert() statement fixes the problem.

    var
      s: AnsiString;
      p: PAnsiChar;
      offset, idx, cnt: Integer;
    begin
      s := 'some>very>long>string>with>field>delimiters>';
      p := @s[1];
      offset := 1;
    
      // find the 5th field
      cnt := 5;
      repeat
        idx := AnsiString.AnsiPos('>', p);
        Inc(p, idx);
        Inc(offset, idx);
        Dec(cnt);
      until cnt = 0;
    
      // insert a new field after the 5th field
      Insert(AnsiString('something new>'), s, offset);
      p := @s[offset];                                 // <- this fixes the issue
    
      // skip other fields
      // insert other values
      // repeat
    end;