Delphi AnsiString Manipulation - PAnsiChar становится поврежденным?

Я использую следующий подход для перемещения и управления файлом AnsiString. В большинстве случаев это работает, но иногда указатель на строку перестает работать. Учитывая следующий код:

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;

При отладке сразу после завершения цикла repeat..until вы можете посмотреть в инспекторе и увидеть, что p = 'field>delimiters>'. После оператора Insert() s = 'some>very>long>string>with>something new>field>delimiters>' и p = 'something new>field>delimiters>' в инспекторе. Это ожидаемо.

Моя реальная строка имеет длину в несколько тысяч символов. Этот метод перемещения по строке и добавления новых полей работает десятки раз, а затем внезапно перестает работать. p больше не показывает вставленное значение в начале строки после вызова Insert(). p, похоже, не знает, что s изменился...

Почему p правильно ссылается на символ в s после большинства операторов Insert() и внезапно перестает работать после некоторых вызовов Insert()?

(Я нашел ответ на свой вопрос, когда набирал его. Ответ кажется очевидным сейчас, но не так, пока я боролся с проблемой. Возможно, публикация вопроса и ответа поможет кому-то еще...)


person James L.    schedule 22.02.2013    source источник
comment
Просто к вашему сведению: как в вашем вопросе, так и в вашем ответе вы никогда не инициализируете offset, прежде чем использовать его в цикле. Таким образом, вы начинаете со случайного содержимого памяти и увеличиваете его на значение idx каждый раз в цикле.   -  person Ken White    schedule 22.02.2013
comment
То, что вы действительно могли бы использовать здесь, если оно существует, — это AnsiPosEx, функция, которая сочетает в себе MBCS-осведомленность AnsiPos с настраиваемым начальным индексом PosEx. Тогда вам вообще не понадобится p, и это избавит вас от создания и уничтожения строк длиной в несколько тысяч символов каждый раз, когда вы передаете p в качестве параметра, который ожидает AnsiString, а компилятор выполняет автоматическое преобразование за вас. Если ваш поисковый запрос действительно состоит из одного символа, рассмотрите возможность использования вместо него AnsiStrScan.   -  person Rob Kennedy    schedule 22.02.2013
comment
Хорошие комментарии. Спасибо, парни. Мой фактический код инициализирует offset - я просто пропустил это здесь. Присмотрюсь еще к AnsiPosEx и AnsiStrScan. Спасибо!   -  person James L.    schedule 22.02.2013


Ответы (1)


Когда вы вызываете Insert(), менеджер памяти переместит AnsiString в новое место в памяти, если не хватает дополнительной непрерывной памяти для расширения буфера в его текущем местоположении памяти. Это оставляет p, указывающее на старую ячейку памяти, которая не содержит измененную строку, и, вероятно, приведет к нарушениям прав доступа.

Добавление одной строки кода для повторной инициализации p после каждого оператора Insert() устраняет проблему.

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;
person James L.    schedule 22.02.2013