1
votes

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...)

1
Just FYI: In both your question and your answer, you never initialize offset before you use it in the loop. Therefore, you're starting with random memory content and incrementing it with the value of idx each time through the loop.Ken White
What you could really use here, if it exists, is "AnsiPosEx," a function that combines the MBCS-awareness of AnsiPos with the configurable start index of PosEx. Then you wouldn't need p at all, and that would save you creating and destroying several-thousand-character-long strings each time you pass p as a parameter that expects an AnsiString and the compiler does the automatic conversion for you. If your search term is really one character, then consider using AnsiStrScan instead.Rob Kennedy
Good comments. Thanks guys. My actual code initializes offset - I just overlooked it here. I'll look at AnsiPosEx and AnsiStrScan too. Thanks!James L.

1 Answers

7
votes

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;