Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Круглый стол
  
Правила КС
>> Настройки

Фильтр вопросов
>> Новые вопросы
отслеживать по
>> Новые ответы

Избранное

Страница вопросов
Поиск по КС


Специальные проекты:
>> К л ю к в а
>> Г о л о в о л о м к и

Вопрос №

Задать вопрос
Off-topic вопросы

Помощь

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  14:44[Войти] | [Зарегистрироваться]
Ответ на вопрос № 43378

Вопросы с аналогичными сообщениями об ошибках:
  • Access violation at address ... (776)

    21-06-2006 06:29
    Понимаю, ошибка «access violation», чаще всего появляется при невнимательном программировании, но в моём случае происходит нечто странное. Имею объект, содержащий в себе дочерние объекты, динамические контейнеры объектов и динамические массивы. Контейнеры заполняются объектами по мере надобности, так же как и динамические массивы данными. При Destroy() каждого объекта приравниваю его динамические массивы к nil, если его контейнеры не равны nil, то для каждого объекта из контейнера вызываю Free(), для объектов созданных в любом случае – вызываю Free(). Т.е. освобождение памяти происходит везде именно в том количестве, в каком надо – очень дотошно проверял. Единственное что не делаю, это не приравниваю к nil локальные динамические массивы в функциях, т.к. они по идее освобождаются автоматически при выходе из области видимости.

    Проблемы выражаются так. При использовании основного объекта-родителя иногда возникает ошибка «access violation», причём всегда в разных местах, иногда при закрытии приложения, а иногда не возникает вообще – при том же исходном коде. С такой особенностью сталкиваюсь впервые в жизни. Иногда при вызове FloatToStr() при вполне допустимом параметре процессор загружается на пару секунд и вываливается опять «access violation». Чаще всего, когда проверяю работу программы пошагово (через F7) проблем не возникает вообще.

    Везде в программе работаю только с памятью, данные заполняю прямо из кода программы. Использую Delphi 7.

    Может кто-то подскажет, где копать и на что обратить внимание? А то не представляю вообще, как найти в чём дело.

    [+] Добавить в избранные вопросы

    Отслеживать ответы на этот вопрос по RSS

    Ответы:


    Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице.
    Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.

    06-08-2007 20:22
    А range checking с динамическими массивами разве не работает? ;) Вот что рекомендую сделать настольной памяткой:

    - Включить в опциях компилятора все проверки
    - Просматривать все хинты и ворнинги компилятора и добиваться их исчезновения
    - В dpr в uses самым первым прописывать такой модуль:

    unit UmemoryLeak;
    interface
    implementation
    uses windows;
    var
    m:integer;
    s:string;
    initialization
    m:=AllocMemSize;
    finalization
    m:=AllocMemSize-m;
    if m<>0
      then
      begin
        str(m,s);
        messagebox(0,PAnsiChar('Утечка памяти '+s+' байт'),'Error!',0);
      end;
    end.



    И этого мало. :) Везде по коду стоит понапихать Assert-ов где только можно и где нельзя. Пока программу пишешь, она постоянно меняется не только в каких-то строчках, а вся вцелом. То, что было верно неделю назад, пятью минутами позже может быть уже не верно, и вот уже семь дней глюк может иметь место быть, но никак не проявляться. После чего код в поисках ошибки недельной давности, допущенной по уже тогда забытым причинам, можно перепахивать хоть до посинения.

    Вот бы ещё модуль типа этого для проверки корректности освобождения ресурсов системы. Но не memproof. ;)

    По поводу Indy, вкладки Samples, форм в DLL - этот модуль сообщит об ошибке, и ошибки действительно есть. В случае с инди разработчики так сделали специально (неужто без этого никак?), а в остальных случаях - просто баги. Перечень, к сожалению, неполный.

    В общем, лично у меня (после возникновения условного рефлекса подключения этого модуля) AV практически никогда не возникает. Если и возникает, то обычно причина практически очевидна. Наконец, специально написана ещё и пушка для стрельбы по воробьям такого рода. ;) Свой менеджер памяти, который нумерует вообще все обращения к get/freemem, включая глубинные вызовы из vcl и выявляет непарные. С целью повторного запуска программ с теми же исходными данными и раздавливания багов точечными ударами. ;)

    Если кому надо будет - http://Janych.selfip.com/examples/delphi/memoryleak/

    Хотя вообще подобных модулей наверное многие понаписали десятки, да ещё есть и всякие memproof-ы, но как по мне - лучше уж моя программа сама себя отладит, при чём для этого мне надо будет положить единственный короткий (и простой) модуль в её каталог и прописать одно слово в uses, чем что-то ставить, запускать, нажимать какие-то кнопочки... А тут можно прямо в модуле написать asm/int 3/end, чтобы вызывать отладчик именно на виновной инструкции. ;)

    21-06-2006 13:05 | Сообщение от автора вопроса
    Спасибо огромное, за очень обстоятельный ответ. К сожалению, точнее сформулировать свой вопрос я не смог, т.к. сам не знал причину проблем. Я проверил свою программу по пунктам на возможные причины возникновения ошибки, и это дало результат. Действительно, оказалось, что в одном месте программы происходило обращение к элементу динамического массива за его пределами. Запуская объект на выполнение в разное время и с разной конфигурацией, проблема то проявлялась (выходило «access violation»), то не проявлялась – запрашивались другие данные в области памяти процесса. Отсюда и такое непредсказуемое поведение. Обнаружить это по логике работы программы было практически невозможно, т.к. массив представлял собой список вещественных чисел. Информация о том, что именно может вызвать такую ошибку, помогли целенаправленно и поэтапно проверить весь код. Спасибо большое!

    Описание Banderas рекомендую всем как настольную памятку. :)

    21-06-2006 07:31
    Если исходники не тайна, можешь выложить? А то так сложно что-то сказать.

    А так могу дать только общую инфу по вопросу.

    Access violation и как с ним бороться

    Севастьянов Андрей
    mystic2000@newmail.ru

    «Access violation at address 0040CF58 in module ‘PROJECT1.EXE’. Read of address FFFFFFFF». Кто никогда не читал это сообщение – тот никогда не программировал на Delphi. С одной стороны, Delphi крайне интенсивно использует динамическую память, а с другой – человеку свойственно ошибаться. Иногда на устранение этой ошибки требуются секунды; но мне также известен случай, когда квалифицированный программист искал причину ошибки полгода…

    Access violation (в переводе «нарушение доступа») возникает при попытке чтения или записи по адресу, который в настоящее время недоступен процессу. Весьма распространенный случай – чтение или запись по адресу FFFFFFFF, то есть попытка обращения к памяти по нулевому указателю. Коварность Access violation заключается в том, что это исключение часто возникает не в месте ошибки, а гораздо позже. Например, произвели мы запись по испорченному адресу. Если повезет – сразу же наткнемся на Access violation. Но ведь эта «случайная» область памяти на самом деле может быть доступна, и занята другим объектом; тогда мы просто незаметно испортим содержимое этого объекта… Подобные ошибки могут долго накапливаться, прежде чем превратятся в Access violation или другое исключение – которое наверняка произойдет в отлаженном и проверенном фрагменте кода.
    Как ловить Access violation?

    Если бы однозначный ответ на этот вопрос существовал, он уже был бы всенародным достоянием. Ниже я приведу список десяти наиболее распространенных (по моему мнению) причин данной ошибки. Этот список не претендует на полноту, и может быть легко расширен. Когда у вас выскочит Access violation – рекомендую пройтись по списку и попытаться определить причину. Кстати, лучше заранее приготовиться к тому, что предстоит отлаживать программу и использовать блоки try...except и try…finally, функции Assigned, Assert и т. п. не только тогда, когда в них возникла необходимость, но и «про запас» – в итоге это наверняка сэкономит время.

    Итак, что может вызвать Access violation?
    1. Вызовы методов еще не созданных объектов.
    2. Вызовы методов объектов, которые не были проинициализированы в результате ошибки.
    3. Вызовы методов объектов, которые уже были уничтожены.
    4. Освобождение объектов, которые будут в дальнейшем использованы и освобождены автоматически.
    5. Выход за пределы диапазона.
    6. Неявное освобождение интерфейсов.
    7. Передача параметров.
    8. Память, выделяемая в DLL [приложении], а освобождаемая в приложении [DLL].
    9. Операции с указателями.
    10. Отсутствие синхронизации при работе с потоками.

    Рассмотрим все эти причины по порядку.

    1. Вызов методов еще не созданных объектов
    Чаще всего мы ссылаемся на еще не созданные объекты в конструкторах и в вызываемых ими обработчиках OnCreate. Самое популярное решение данной проблемы – изменить порядок инициализации форм (компонент на форме), что, однако, не рекомендуется из соображений понятности кода.
    Второй вариант – перенести код, вызывающий Access violation, в обработчик более позднего события (на момент которого объекты уже созданы). Для формы это может быть, скажем, OnShow. В крайнем случае – объект всегда может послать сам себе сообщение, которое будет обработано уже после того, как будут проинициализированы все объекты.
    Третий вариант – перед обращением к объекту каждый раз проверять, существует ли он.
    Поскольку большинство объектов инициализируется значением nil – исключение как раз и возникает в том месте, где мы обратились к несуществующему объекту, что упрощает задачу.
    Сюда же отнесем и такую распространенную среди новичков ошибку, как неправильный вызов конструктора: SomeObject.Create (правильно SomeObject := TSomeObject.Create).
    В большинстве таких случаев компилятор сам выдаст предупреждение «Variable XXX might not have been initialized», к которому нужно относиться с должным почтением, особенно если переменная является объектом; хотя иногда бывает, что компилятор волнуется понапрасну.

    2. Вызовы методов объектов, которые не были проинициализированы в результате ошибки
    Многие функции API и некоторые функции VCL возвращают nil в случае, если объект не был по какой-либо причине создан, найден и т.п. Естественно, что попытка обращения к такому объекту вызовет Access violation. Этот случай также легко ловится, а для исправления обычно используют проверку возвращаемого значения, иногда с вызовом более понятного и адекватного исключения.
    Ошибка часто возникает в случае, когда вместо вызова метода Free объекта происходит прямое обращение к деструктору. Такое случается, например, если исключительная ситуация произошла в конструкторе – тогда сразу же после вызова конструктора произойдет вызов деструктора, который может «споткнуться» на одном из вызовов Destroy объекта, до которого не дошла очередь при инициализации.

    3. Вызовы методов объектов, которые были уже уничтожены
    Еще одно место, где возникают ошибки подобного рода – деструкторы (и вызываемые ими обработчики сообщений OnDestroy). Этот случай похож на первый. Для решения проблемы здесь также можно поиграть с порядком создания элементов, так как уничтожаются они строго в обратном порядке. Иногда может помочь флаг csDestroying, расположенный в свойстве ComponentState, который говорит о том, что идет вызов деструктора компонента, а значит – не все операции допустимы.
    Полезное средство для обнаружения этой ошибки (в случае объектов) – вызов функции FreeAndNil(Obj) вместо Free.Obj, поскольку первая не только освобождает объект, но и обнуляет указатель на него. Это предохраняет сразу от двух неприятностей: во-первых, указатель на объект будет обнулен, и вы никак не сможете испортить содержимое памяти других объектов или менеджера памяти; а во-вторых – при попытке обращения к объекту возникнет исключительная ситуация по адресу FFFFFFFF, что облегчит поиск ошибки. Однако FreeAndNil – не панацея от всех бед; ведь могут существовать и другие указатели на наш объект, и обнуление одного не поможет.
    Есть еще одна причина возникновения такой ситуации: некоторые объекты в своих деструкторах автоматически уничтожают другие объекты; и излишнее рвение в таком нужном деле, как уборка мусора, может привести к тому, что вы дважды вызовете чей-то деструктор. Сказанное относится в первую очередь к компонентам: каждый компонент уничтожает все те компоненты, которыми он владеет; поэтому следующий код приведет к ошибке:

    constructor TSomeObject.Create(AOwner: TComponent);
    begin
    inherited;
    FInnerObj := TInnerObj.Create(AOwner); // Будьте осторожны !!!
    { Теперь владельцем FInnerObj является AOwner }
    end;

    destructor TSomeObject.Destroy;
    begin
    FinnerObj.Free; // Ошибка !!!
    end;

    Что происходит при уничтожении владельца экземпляра TSomeObject? Он владеет как самим этим экземпляром, так и объектом FInnerObj. Поэтому в деструкторе владельца будут освобождены как сам экземпляр TSomeObject, так и FInnerObject, что приведет к двойному уничтожению объекта FInnerObj (один раз в деструкторе его владельца, другой раз в деструкторе TSomeObject). Access violation…

    Варианты решения проблемы такие:

    { Вариант 1 }
    constructor TSomeObject.Create(AOwner: TComponent);
    begin
    inherited;
    FInnerObj := TInnerObj.Create(Self);
    { Теперь наш компонент является владельцем FInnerObj }
    end;

    destructor TSomeObject.Destroy;
    begin
    { Объект FInnerObj имеет владельца и уничтожится автоматически }
    end;

    { Вариант 2 }
    constructor TSomeObject.Create(AOwner: TComponent);
    begin
    inherited;
    FInnerObj := TInnerObj.Create(nil);
    { FInnerObj не имеет владельца}
    end;

    destructor TSomeObject.Destroy;
    begin
    FinnerObj.Free; // FInnerObj не имеет владельца.
    { Удаляем вручную. }
    end;

    4. Освобождение объектов, которые будут в дальнейшем использованы и освобождены автоматически
    Что называется, не вами и не здесь... Delphi автоматически уничтожает большинство своих объектов. Но даже несмотря на это, обычно уничтожение объекта не приводит к ошибке, так как в деструкторе он сам вычеркнет себя из всех списков, где он фигурировал.
    Однако это верно далеко не всегда; особенно если вы пользуетесь собственной системой классов. И, естественно, если объект будет использован в дальнейшем – тем более нельзя его удалять. Примеры – освобождение класса исключения в секции finally и освобождение объекта Sender при вызове события.
    Локализовать такую ошибку можно так: комментировать вызовы Free (FreeAndNil), и, если при очередном комментировании ошибка исчезла – значит, она жила в закомментированном фрагменте.

    5. Выход за пределы диапазона
    Данная ошибка приводит к исключительной ситуации Range Check Error, но только при включенной опции проверки диапазона {$R+}. Если эта опция выключена (а по умолчанию это именно так) – может произойти запись либо в область памяти, занимаемую другой переменной или классом, либо в свободный фрагмент памяти. Во втором случае возможны варианты – либо этот фрагмент памяти выделен операционной системой, либо не выделен. В последнем случае вам повезло, так как Access violation появится сразу же, и вы будете точно знать место ошибки. А на этапе отладки – лучше включать опцию {$R+}…

    6. Неявное освобождение интерфейсов
    Delphi предоставляет удобные средства для работы с интерфейсами. Но не все интерфейсы требуют автоматического уничтожения при выходе из области видимости. Например, в случае объектов DirectDraw, IBackBuffer будет уничтожен вместе с интерфейсом IPrimarySurface, поэтому вы, следуя документации, разумно его не уничтожаете; но Delphi сделает это за вас, что приведет к ошибке.
    Выходы: либо уничтожать интерфейс непосредственно перед уничтожением его родителя, либо (если это невозможно) просто обнулять его вызовом FillChar(SomeInterface, SizeOf(SomeInterface), 0). Место такой ошибки легко определяется в отладчике.

    7. Передача параметров
    При передаче бестиповых параметров совместимость типов не проверяется, что также может привести к Access violation. Спасти может только обыкновенная внимательность. Самые распространенные ошибки – передача неразыменованых указателей, лишнее использование операции взятия адреса @, и т.д. Например:

    var
    P: Pointer;
    S: TStream;

    GetMem(P, 100);
    S.Read(S, P); // Ошибка
    S.Read(S, P^); // Правильно

    Еще о передаче параметров: когда вы используете DLL, в объявлении функций, описанных в ней, компилятор всецело полагается на вас. Ошибка в описании функции приведет к тому, что вам не удастся ее вызвать! И не забудьте служебное слово stdcall, так как оно влияет на последовательность передаваемых параметров.

    8. Память, выделяемая в DLL [приложении], освобождается в приложении [DLL]
    Рассмотрим ситуации, когда память, выделяемая в DLL, освобождается в приложении, и память, выделяемая в приложении, освобождается в DLL. Прежде всего, надо иметь в виду, что модуль System (в котором реализовано управление памятью) будет подключен дважды – один раз в программе, а другой раз в DLL. Соответственно, у вас будет и два менеджера памяти – один в основной программе, другой в DLL. И вполне естественно, что попытка освобождения памяти, принадлежащей другому менеджеру, вызовет недоумение.
    Выход – делать в точности так, как предписывает комментарий, который автоматически вставляется при создании DLL при помощи мастера: подключите первым модулем в проекте как приложения, так и DLL модуль ShareMem. При этом появится новый менеджер памяти, расположенный в Borlndmm.dll. И не забывайте о неявных операциях с памятью, которые осуществляют строки, динамические массивы, классы, и т.п. В некоторых случаях подключать модуль ShareMem не обязательно, но если вы в этом не уверены – лучше перестраховаться.
    Кстати, в Delphi 6 появилась возможность перекомпилировать модуль ShareMem таким образом, чтобы он использовал не встроенный менеджер памяти, а функцию GlobalAlloc. Это отличный способ решить проблему Access violation, если она возникает при связи приложения и DLL, реализованных на разных компиляторах от разных фирм. Но это лишь дополнительное средство, а не решение проблемы.

    9. Операции с указателями
    Тут уж, как говориться, сам бог велел. Если вы реализуете сложный алгоритм, связанный с перемещением указателей и тому подобным – я удивлюсь, если вам удалось ни разу не прописать значение в «левую» область памяти. Но нет худа без добра – значит, вы подготовлены к встрече с Access violation и готовы с ней сражаться – иначе не стоило бы этим делом и заниматься. Отладка программы в этом случае ничем не отличается от отладки любых других алгоритмов, и Access violation становиться здесь рабочим исключением. В силу большой общности вопроса тут сложно дать какие-либо рекомендации.

    10. Отсутствие синхронизации при работе с потоками
    Пожалуй, наиболее неприятное место. Если вы догадываетесь, что у вас складывается именно эта ситуация – примите мои соболезнования. Типичные признаки: в системе несколько потоков, и Access violation возникает в разных местах, даже если вы не вносили изменений в программу. Но прежде чем усиленно медитировать на тему того, какие два (или более) потоков у вас передрались, проделайте вот что:
    1. Удостоверьтесь, что значение переменной IsMultithread – True (глобальная переменная, обеспечивающая безопасное использование динамической памяти). Это важно, если вы создаете поток при помощи CreateThread. Если вы пользуетесь вызовами BeginThread – значение IsMultiThread не критично. Пока один поток управляет памятью, доступ к диспетчеру памяти для другого потока будет заблокирован – до того момента, пока первый поток не освободит диспетчер памяти.
    2. Помните, делать внутренний подсчет ссылок (строки, динамические массивы, интерфейсы и т.д.) при помощи переменных не вполне безопасно. Проанализируйте ваш код на этот предмет.
    3. Если вы используете классы и интерфейсы – проверьте еще раз, что интерфейс поддерживает многопоточные обращения к себе. Часто это не так.
    Что делать, если ничего не помогло

    У Access violation есть типичная и наиболее неприятная особенность: исключение может проявляться не в момент возникновения ошибки, а гораздо позже. Найти именно ту строку, которая приводит к ошибке, бывает непросто. Для этого удобно использовать функцию GetHeapStatus, которая (кроме прочей статистики) даст вам ответ – повреждены ли структуры менеджера памяти (HeapErrorCode). Можете смело считать, что ошибка в программе произошла раньше, чем в HeapErrorCode занеслось ненулевое значение. Вставляйте эту проверку во все критические места программы, и пусть вам повезет!

    «Коли шансы на нуле, ищут злато и в золе…»
    Пусть это будет вашим последним средством, и не стоит прибегать к нему без крайней на то необходимости. В модуле System.pas есть еще пара функций, которые могут быть полезны:

    THeapBlock = record
    Start: Pointer;
    Size: Cardinal;
    end;

    THeapBlockArray = array of THeapBlock;
    TObjectArray = array of TObject;

    function GetHeapBlocks: THeapBlockArray;
    function FindObjects(AClass: TClass; FindDerived: Boolean): TObjectArray;

    Функция GetHeapBlock возвращает в массиве список всех блоков, выделенных в настоящей момент программой, а функция FindObjects возвращает список всех созданных объектов, которые были порождены от указанного типа. Чтобы воспользоваться этими функциями, надо перекомпилировать модуль System.pas с предопределенным символом DEBUG_FUNCTIONS (в случае Delphi 6; а в случае Delphi 5 – предварительно добавить приведенные выше строки в интерфейсную часть).

    Удачной вам локализации и исправления ошибок!

    Добавьте свое cообщение

    Вашe имя:  [Войти]
    Ваш адрес (e-mail):На Королевстве все адреса защищаются от спам-роботов
    контрольный вопрос:
    Зимой — белый, летом — серый. Кто?
    в качестве ответа на вопрос или загадку следует давать только одно слово в именительном падеже и именно в такой форме, как оно используется в оригинале.
    Надоело отвечать на странные вопросы? Зарегистрируйтесь на сайте.
    Тип сообщения:
    Текст:
    Жирный шрифт  Наклонный шрифт  Подчеркнутый шрифт  Выравнивание по центру  Список  Заголовок  Разделительная линия  Код  Маленький шрифт  Крупный шрифт  Цитирование блока текста  Строчное цитирование
  • вопрос Круглого стола № XXX

  • вопрос № YYY в тесте № XXX Рыцарской Квинтаны

  • сообщение № YYY в теме № XXX Базарной площади
  • обсуждение темы № YYY Базарной площади
  •  
     Правила оформления сообщений на Королевстве

    Вопросы с аналогичными сообщениями об ошибках:
  • Access violation at address ... (776)


    Страница избранных вопросов Круглого стола.
  •   
    Время на сайте: GMT минус 5 часов

    Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
    Функция может не работать в некоторых версиях броузеров.

    Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
    Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

     
    © При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
    Все используемые на сайте торговые марки являются собственностью их производителей.

    Яндекс цитирования