2013-09-04 1 views
23

У меня есть некоторые неожиданные нарушения доступа для кода 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) показывает «Привет» (а иногда возникает нарушение прав доступа при освобождении объектов, используемых для реализации анонимных процедур).

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

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

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

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

Update:

На основе замечаний 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] - как к внешним локальным переменным вложенных методов доступно доступ к, к которым обращаются локальные переменные.

+0

В моих тестах я получил это предупреждение «[Предупреждение DCC] Unit1.pas (45): W1036« $ frame »переменной« возможно, не был инициализирован ». Поскольку я не объявлял какую-либо переменную $ frame, я предполагаю, что она была сгенерирована компилятором при объявлении интерфейсов, реализующих анонимные методы. Предупреждение подсказывает, что не все было сделано правильно компилятором, так что это кажется ошибкой. Изменение кода, чтобы объявление переменной S в качестве строки заставляло проблему проявлять себя раньше. Отладка предполагает, что переменная S не была должным образом обработана сгенерированным кодом. – AlexSC

+0

@AlexSC «Возможно, не было инициализировано» обнаружение, как известно, плохо, существует множество ложных срабатываний, которые не указывают на какую-либо реальную проблему и не влияют на сгенерированный код, поэтому это предупреждение, которое должно быть безопасным для игнорирования. Я также могу получить это предупреждение (включая переменную, генерируемую компилятором '$ frame') в более простом коде, который работает корректно. – hvd

+1

Скомпилирует и работает нормально в XE2 –

ответ

0

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

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

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

Согласно @Sertac Акюз это было зафиксировано в XE2.

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

+0

Извините, но это не отвечает на мой вопрос. «Это непознаваемо, очевидно.»? В самом деле? Это ошибка, которая возникает для определенных комбинаций вложенных функций и анонимных функций, и кто-то, у кого больше навыков или знаний, чем я, несомненно, будет более подробно описывать эти «определенные комбинации», что я и спрашиваю. Их не нужно полностью избегать. – hvd

+1

У меня есть один прецедент, где их сложно избежать, не усложняя код: функция, которая возвращает ссылку на процедуру, которая передается и заканчивается вызовом другого кода. Сгенерированный компилятором вспомогательный объект освобождается автоматически, так как тип ссылки ссылается на процедуру подсчета. С 'процедурой объекта', мне нужно будет вручную добавить много дополнительного кода, чтобы обеспечить освобождение объекта. Единственной практической альтернативой было бы вручную определить «интерфейс» и реализовать его вспомогательный класс. Java имеет сбор мусора, поэтому это не проблема. – hvd

1

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

Таким образом, кажется, что 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.

Окончательный вывод:

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

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

+0

Уверяю вас, я знаю, как работают указатели. Я знаю, что переменные указателя не содержат указательных данных. Но строковые литералы не пересчитываются, они существуют непосредственно в исполняемом образце, и нет данных, которые нужно скопировать, только сам указатель. Я не использовал 'string', потому что не управляемые типы, такие как' PChar', упрощают сборку, что упрощает проверку собранной сборки. Но он также не работает при использовании 'string', и на самом деле вы можете увидеть проблему, даже если используете переменную типа Integer. – hvd

+0

Возможно, это тоже не удается, но вы все еще не можете просто назначить «Привет, мир!». на такой PChar, потому что это всего лишь указатель на данные, но в приведенном выше примере нет данных, на которые он указывает. Если он все еще терпит неудачу с объявлением 'String' или' Integer', то, возможно, переменная исчезает где-то или внезапно больше не виден для proc. Полный ASM-источник всего Proc поможет выявить, что на самом деле происходит иначе, и проследить, где переменная исчезает или где именно происходит нарушение доступа, чтобы сузить ее. – Amenominakanushi

+0

Как я уже сказал, строковые литералы не пересчитываются, они не исчезают. Вы можете держать указатель на строковый литерал без каких-либо проблем. Это не просто детали реализации, это явное обещание, сделанное в документации, на которую можно положиться. [Читайте здесь.] (Http://docwiki.embarcadero.com/RADStudio/XE/en/Internal_Data_Formats#Long_String_Types) Что касается проверки сгенерированной сборки, я сделал это уже, и я показал соответствующие детали в вопросе. Переменная не исчезает, и я уже выяснил, где именно происходит нарушение прав доступа. – hvd

Смежные вопросы