Итак, для всех прочих (а именно: разработчиков Delphi/C++ Builder, пытающихся решить проблему возникновения исключения Access Violation в своей программе) - приступим! Исключение класса EAccess.
Violation - это самое частое исключение в Delphi- программах. Я хотел бы рассмотреть, что это такое, когда возникает, и как с ним бороться.
Этот пост скорее для начинающих, поэтому данные могут излагаться с упрощением. Примечания. если вы совсем начинающий или студент/студентка и получили Access Violation - первым делом включите опцию Range Check Errors (Project/Options, вкладка Compiler) и сделайте Project/Build. IDE (или даже не знаете, что это такое) - прочитайте сначала эту статью. Память занимает любая переменная в вашей программе. Будь это форма, компонент, массив, запись, строка или же простой Integer.
Под некоторые переменные память выделяется автоматически (например, под переменные типа Integer и статические массивы), под другие - вы должны выделять её сами, явно (например, динамические массивы). Собственно, с точки зрения операционной системы каждая переменная характеризуется адресом в памяти (местоположением) и размером. Понятно, что обычно данные разных переменных не пересекаются - за исключением случаев обращением к одной области памяти через разные имена с помощью указателей. Грубо говоря, обычно в программе используется три типа памяти: область памяти для глобальных переменных, стек и куча. Память для глобальных переменных выделяется загрузчиком ОС при загрузке исполняемого модуля программы в память и освобождается при выгрузке модуля (выходе из программы). Глобальные переменные - это любые переменные, объявление которых располагается вне класса или процедуры. Стек используется для размещения локальных переменных (объявленных в процедуре/функции) и служебных данных (типа адресов возврата и адресов обработчиков исключений).
Куча же используется для размещения динамических данных. Подробнее. Заметим, что для переменных динамических типов данных (динамические массивы, строки, любые объекты, компоненты), хотя сама переменная может размещаться в области для глобальных переменных или в стеке (а, значит, память для неё выделяется автоматически), но данные, на которые она указывает, всегда размещаются в куче и, зачастую, должны управляться вручную. Вне зависимости от того, кто выделяет память для переменной (вы вручную или компилятор автоматически), память для любой переменной должна быть выделена перед использованием, а потом, когда переменная уже не будет нужна - освобождена. Иногда из- за ошибок в коде программы происходит ситуация, когда программа при выполнении пытается получить доступ к памяти, которая не была выделена или уже была освобождена. Когда такое происходит, процессор возбуждает исключение класса EAccess.
Violation. Обычный текст ошибки в приложении Delphi - . Попытка записи/чтения в ZZZ. Хотя причина этого исключения всего одна (попытка обращения к недействительной памяти), но эта ошибка может проявлять себя в весьма разном виде и коде.
Более подробно об указателях и памяти говорится в уже упоминавшейся выше статье. Ищем место возникновения Access Violation. Как, собственно, бороться с этими ошибками? Ну, если вы получили EAccess. Презентация Россия 2 Класс далее. Violation под отладчиком: То нужно просто нажать на .
Также можно посмотреть стек вызовов (в меню Delphi - View/Debug windows/Call Stack): В этом окне будет показано, как же вы туда попали. Читается это дело сверху вниз (текущее место помечено стрелочкой). Можно дважды щёлкать по строкам в этом окне для перехода в код, соответствующий этой строке. Иными словами, отладчик сразу же тыркает вас в строку с ошибкой. Если же вы используете средства автоматической диагностики типа Eureka.
Log/mad. Except, то вместо обычного сообщения об ошибке вы получите баг- отчёт, в котором будет виден тот же самый Call Stack (вид стека вызова может отличаться из- за различных методов его получения): Не имеет значения, столкнулись ли вы с проблемой во время отладки или получили баг- отчёт от Eureka. Log для уже распространяемой программы - хорошо бы подготовиться к этой ситуации заранее и включить опции проекта, упрощающие отладку. Как правило, это опции . Определить почему же в этой строке возникла ошибка - это вторые пол- дела. Ищем причину возникновения Access Violation анализом кода. Если ситуация возникла у вас в отладчике, то тут всё относительно просто: вам нужно установить точку останова на проблемную строчку и проверить значения всех переменных и выражений, участвующих в ней - вот вам и причина ошибки, находится сразу же.
Я не буду подробно останавливаться на теме отладки здесь, более подробно об этом написано в моей статье, часть 2 (осторожно: большой размер). В случае, если у вас на руках есть только баг- репорт, а не ситуация под отладчиком, то вам придётся использовать свои телепатические способности, которые обычно развиваются с опытом. Дабы помочь вам в этом, здесь я как- раз и хочу рассмотреть типичные причины возникновения ошибки Access Violation. Во- первых, это всевозможные ошибки выхода за границы массивов.
Например, типичная ошибка новичка может выглядеть так: var. Должно быть: for X : = 0 to Length(List) - 1 do. Дело в том, что подобные ошибки весьма опасны тем, что могут пройти незамеченными (и потом редко ловятся при эксплуатации программы), даже более того - они могут разрушить стек, так что нельзя будет получить место возникновения ошибки. Но об этом позже. Различного рода неверные передачи параметров. Обычно эти ошибки отлавливаются во время разработки и тестирования, нежели во время эксплуатации программы. Чаще всего они возникают при использовании процедур с нетипизированными параметрами.
Сюда же относятся различные варианты ошибок переполнения буфера, например: var. S1: array of Integer. Передачи данных между двумя менеджерами памяти.
Обычно ошибки такого плана возникают при передаче данных из DLL в приложение или наоборот. Чаще всего новички любят передавать из/в DLL строки типа String. Причины этого я рассматривал ранее. Эти ошибки обычно отлавливаются немедленно во время разработки программы и очень редко доживают до рабочей программы. Решаются эти проблемы правильным проектированием. Неверное объявление функций, импортируемых из DLL. Наиболее часто путают модель вызова.
Если у вас получается EAccess. Violation при вызове функции из DLL - просто внимательно посмотрите на её объявление и убедитесь, что её сигнатура верна - чаще всего пропускают модель вызова, stdcall или cdecl. Хотя обычно ошибки такого плана отлавливаются на этапе разработки, тем не менее могут быть ситуации, когда ошибка проползает в готовую программу. Вот увлекательная история Реймонда Чена о том, как программа может работать с неверно объявленным прототипом функции (довольно интересны и посты в серии до и после этого). Отсутствие синхронизации при работе с потоками. Если вы делаете программу с использованием нескольких потоков, то у вас могут быть проблемы, если вы не обеспечили необходимой синхронизации. Например, любые обращения к VCL запрещены из вторичных потоков - вам нужно использовать Synchronize.
Собственно, проблемы тут возникают, когда один поток меняет данные с которыми работает второй поток - что для последнего становится полной неожиданностью.