Подтвердить что ты не робот

Неверный код при объединении анонимных и вложенных процедур

У меня есть некоторые неожиданные нарушения доступа для кода Delphi, которые, по моему мнению, верны, но, похоже, ошибочны. Я могу уменьшить его до

procedure Run(Proc: TProc);
begin
  Proc;
end;

procedure Test;
begin
  Run(
    procedure
    var
      S: PChar;

      procedure Nested;
      begin
        Run(
          procedure
          begin
          end);
        S := 'Hello, world!';
      end;

    begin
      Run(
        procedure
        begin
          S := 'Hello';
        end);
      Nested;
      ShowMessage(S);
    end);
end;

Что происходит для меня, так это то, что S := 'Hello, world!' хранится в неправильном месте. Из-за этого возникает либо нарушение доступа, либо ShowMessage(S) показывает "Hello" (а иногда возникает нарушение прав доступа при освобождении объектов, используемых для реализации анонимных процедур).

Я использую Delphi XE, все обновления установлены.

Как я могу узнать, где это вызовет проблемы? Я знаю, как переписать мой код, чтобы избежать анонимных процедур, но мне трудно понять, в каких ситуациях они приводят к неправильному коду, поэтому я не знаю, где их избежать.

Мне было бы интересно узнать, исправлено ли это в более поздних версиях Delphi, но не более чем интересно, обновление на данный момент не является вариантом.

В QC, в последнем отчете я могу найти аналогичный # 91876, но это разрешено в Delphi XE.

Обновление

На основе комментариев AlexSC с небольшой модификацией:

...

      procedure Nested;
      begin
        Run(
          procedure
          begin
            S := S;
          end);
        S := 'Hello, world!';
      end;

...

работает.

Сгенерированный машинный код для

S := 'Hello, world!';

в неудачной программе

ScratchForm.pas.44: S := 'Hello, world!';
004BD971 B89CD94B00       mov eax,$004bd99c
004BD976 894524           mov [ebp+$24],eax

тогда как правильная версия

ScratchForm.pas.45: S := 'Hello, world!';
004BD981 B8B0D94B00       mov eax,$004bd9b0
004BD986 8B5508           mov edx,[ebp+$08]
004BD989 8B52FC           mov edx,[edx-$04]
004BD98C 89420C           mov [edx+$0c],eax

Сгенерированный код в отказоустойчивой программе не видит, что S был перемещен в класс, сгенерированный компилятором, [ebp+$24] - это , к которому обращаются внешние локальные переменные вложенных методов, как локальные переменные доступны.

4b9b3361

Ответ 1

Не просматривая весь код Ассемблера для всего теста процедуры (и только в том случае, если вы предположили, что на фрагменте, который вы опубликовали), вероятно, что на отказоустойчивом Snippet был перемещен только указатель, где на правильной версии есть некоторые данные.

Итак, кажется, что S: = S или S: = '' заставляет компилятор создавать ссылку своим собственным и даже выделять некоторую память, что объясняет, почему она работает тогда.

Я также предполагаю, что нарушение прав доступа происходит без S: = S или S: = '', потому что, если для String нет выделенной памяти (помните, что вы только объявили S: PChar), то возникает нарушение доступа, доступ к незанятой памяти.

Если вы просто объявите S: String, это, вероятно, не произойдет.

Дополнения после расширенного комментария:

PChar - это только указатель на структуру данных, который должен существовать. Еще одна распространенная проблема с PChar заключается в том, чтобы объявлять локальные переменные, а затем передавать PChar на эту переменную в другие Procs, потому что происходит то, что локальная переменная освобождается после завершения процедуры, но PChar все равно укажет на нее, Нарушения доступа после доступа.

Единственная возможность, существующая для Документации, объявляет что-то вроде этого const S: PChar = 'Hello, world!', это работает, потому что компилятор может разрешить относительный адрес. Но это работает только для констант, а не для переменных, как в примере выше. Выполнение этого, как в приведенном выше примере, требует хранения для строкового литерала, которому PChar затем указывает на значение S:String; P:PChar; S:='Hello, world!'; P:=PChar(S); или подобное.

Если он все еще терпит неудачу с объявлением String или Integer, возможно, переменная исчезает где-то рядом или внезапно больше не видна в proc, но это будет другой вопрос, который уже не имеет никакого отношения к существующей проблеме PChar./p >

Окончательное заключение:

Возможно выполнение S:PChar; S:='Hello, world!', но компилятор затем просто выделяет его как локальный или глобальный констант, например const S: PChar = 'Hello, world!', который сохраняется в исполняемом файле, второй S := 'Hello' затем создает другой, который также сохраняется в исполняемом файле и и так далее, но S затем указывает только на последний выделенный, все остальные все еще находятся в исполняемом файле, но не доступны, не зная точное местоположение, потому что S указывает только на последний выделенный.

Таким образом, в зависимости от того, какой из последних был S, он указывает на Hello, world! или Hello. В приведенном выше примере я могу только догадываться, какой из них был последним, и кто знает, возможно, компилятор может только догадываться и в зависимости от оптимизаций и других непредсказуемых факторов S может внезапно указать на нераспределенный Mem вместо последнего на Time Showmessage(S), который затем вызывает нарушение доступа.

Ответ 2

Как узнать, что это вызовет проблемы?

Трудно сказать в этот момент времени.
Если бы мы знали природу исправления в Delphi XE2, мы были бы в лучшем положении.
Все, что вы можете сделать, это воздерживаться от использования анонимных функций.
Delphi имеет процедурные переменные, поэтому необходимость в анонимных функциях не так уж и страшна.
См. http://www.deltics.co.nz/blog/posts/48.

Мне было бы интересно узнать, исправлено ли это в более поздних версиях Delphi

Согласно @Sertac Akyuz, это было исправлено в XE2.

Лично мне не нравятся анонимные методы, и мне пришлось запретить пользователям использовать их в моих проектах Java, потому что значительная часть нашей базы кода была анонимной (обработчики событий).
Используемый в экстремальной умеренности, я вижу пример использования.
Но в Delphi, где у нас есть процедурные переменные и вложенные процедуры... Не так много.