Ошибки в программе delphi

Ошибки — неизбежное зло программирования. Видимо пока трудно даже представить средство с помощью которого можно избавится от них. Человеку, которые выдумает это чудодейственное лекарство, благодарные потомки-программисты, несомненно, воздвигнут памятник. Пока же остается лишь заниматься обычным делом: ловлей багов. «Нарушение Доступа» — фраза, которую пользователи видят, когда приложение делает попытки обратиться к памяти, которая не обозначена для их использования — и как следствие происходит сбой в работе программы:

Access violation at address in module . Read of address Ситуация при которой Windows давала бы полную свободу программам — записывай данные куда хочешь, скорее всего бы привела к разноголосице программ и полной потери управления над компьютером. Но этого не происходит — Windows стоит на страже «границ памяти» и отслеживает недопустимые операции. Если сама она справиться с ними не в силах — происходит запуск утилиты Dr. Watson, которая записывает данные о возникшей ошибки, а сама программа закрывается.

АВТО — ЛЮБАЯ МОДЕЛЬ ВЫПУСКА ДО 2020 ГОДА — САМОСТОЯТЕЛЬНО ДИАГНОСТИКА , ОШИБКИ (?)- AUTOCOM 20.23


Известно что, при программирование, особенно крупных программных продуктов, уследить за всеми процессами в коде невозможно, да и нет необходимости. Использование сторонних компонентов и библиотек только усложняет дело. Именно поэтому программисты Delphi, порой и сталкиваются со «своенравными» программами, которые то и дело норовят «сбросить пользователя». Итак, давайте рассмотрим некоторые вопросы, связанные с корректной среды программирования, так и непосредственно проблемы написания кода, которые ведут к возникновению ошибок типа «ошибка доступа» (AVS) и очертим наиболее известные пути их исправления.
Мы можем поделить AVS, с которыми сталкиваются при разработке в Delphi на два основных типах: ошибки при выполнения и некорректная разработка проекта, что вызывает ошибки при работе программы.
Ошибки возникают при старте и закрытии Delphi или формировании проекта. Причиной могут являться сбои в «железе» компьютера.
Эти ошибки могут быть вызваны различными источниками, включая систему BIOS, операционную систему или аппаратные подпрограммы драйверов. Некоторые видео-, звуковые или сетевые платы могут фактически вызывать подобного рода ошибки в Delphi. Для решения подобных аппаратных проблем можно предпринять последовательность неких «стандартных» ходов:
проверить, что не имеется никаких конфликтов между установленными устройствами, устранить обнаруженные конфликты;
попробовать слегка уменьшить «аппетита» видеодрайвера — поставить меньшее разрешение;
в случае если у вас двухпроцесорная система обеспечить равное изменение шага для каждого процессора;
И в конце концов просто попытаться заменить драйвера на более свежие.

Но помимо чисто железных проблем — большую головную боль могут вызвать ошибки в работе программного обеспечения. Особенно это касается непосредственно операционной системы. Зачастую Windows терпит крах спонтанно. Вот рекомендации которые помогут вам создать более устойчивую среду программирования:

Как сделать регистрацию в программе (Delphi)


Хотя Windows 9X популярная система, разработку лучше проводить в Windows NT или Windows 2000 — это более устойчивые операционные системы. Естественно при переходе на них придется отказаться от некоторых благ семейства Windows 95/98/Me — в частности не все программы адоптированы для Windows NT/2000. Зато вы получите более надежную и стабильную систему.
Не забывайте о том, как важно всегда иметь под рукой свежие версии компонентов для Delphi и дополнительных библиотек. В отличие от Windows создатели данных пакетов стараются от версии к версии уменьшать количество ошибок.
Следите за тем, что бы устанавливаемые компоненты были предназначены непосредственно для вашей версии Delphi. Попробуйте деинсталлировать чужеродные компоненты один за другим (или пакет за пакетом) пока проблема не будет устранена.
Контролируйте все программные продукты установленные на вашей машине и деинсталлируйте те из них, которые сбоят. Фаворитами AV среди них являются шароварные утилиты и программы и бета версии программных продуктов.
Все вышеперечисленное в основном не касалось самого процесса программирования и в малой степени зависит от разработчика. Теперь же обратимся к теме, как не допустить при разработки программного продукта ситуации при которой, он сам будет являться причиной ошибки.
Вы могли бы рассмотреть компилирование вашего приложения с директивой , данная директива компилятора может создавать файлы карты (файлы с расширением map, которые можно найти в том же каталоге, что и файлы проекта), которые могут послужить большой справкой в локализации источника подобных ошибок. Для лучшего «контроля» за своим приложением, компилируйте его с директивой . Таким образом, вы заставите Delphi генерировать информацию для отладки, которая может послужить подспорьем при выявление возникающих ошибок.
Следующая позиция в Project Options — Linker var BadForm: TBadForm; begin BadForm.Refresh; // причина ошибки end;

Попытаемся разобратся в этой ситуации. Предположим, что BadForm есть в списке «Available forms » в окне Project Options|Forms. В этом списке находятся формы, которые должны быть созданы и уничтожены вручную.

В коде выше происходит вызов метода Refresh формы BadForm, что вызывает нарушение доступа, так как форма еще не была создана, т.е. для объекта формы не было выделено памяти. Если вы установите «Stop on Delphi Exceptions » в Language Exceptions tab в окне Debugger Options, возможно возникновения сообщение об ошибке, которое покажет, что произошло ошибка типа EACCESSVIOLATION. EACCESSVIOLATION — класс исключение для недопустимых ошибок доступа к памяти. Вы будете видеть это сообщение при разработке вашего приложения, т.е. при работе приложения, которое было запущено из среды Delphi. Следующее окно сообщения будет видеть пользователь — и программа будет закрыта при совершение недопустимой операции:

Access violation at address 0043F193 in module ‘Project1.exe’ Read of address 000000.

Первое шестнадцатиричное число (‘0043F193’) — адрес ошибки во время выполнения программы в программе. Выберите, опцию меню ‘Search|Find Error’, введите адрес, в котором произошла ошибка (‘0043F193’) в диалоге и нажмите OK.

Читайте также:
Отличие курса от программы

Теперь Delphi перетранслирует ваш проект и покажет вам, строку исходного текста, где произошла ошибка во время выполнения программы, то есть BadForm.Refresh. Естественно, что списка наиболее общих причин ошибок, вызывающих аварийное завершение работы программы, написанной в Delphi в чистом виде нет. Есть несколько общих «узких мест» в коде и структуре программы, когда подобная ошибка может произойти. Перечислим наиболее распространенные.

Недопустимый параметр API

Если вы пытаетесь передать недопустимый параметр в процедуру Win API, может произойти ошибка. Необходимо отслеживать все нововведения в API при выходе новых версий операционных систем и их обновлений.

Уничтожение исключения

Никогда не уничтожайте временный объект исключения. Обработка исключения автоматически уничтожает объект исключения. Если вы уничтожите объект самостоятельно, то приложение попытается уничтожать объект снова, и произойдет ошибка.

Zero:=0; try dummy:= 10 / Zero; except on E: EZeroDivide do MessageDlg(‘Can not divide by zero!’, mtError, [mbOK], 0); E.free. // причина ошибки end;

Индексация пустой строки Пустая строка не имеет никаких достоверных данных. Следовательно, попытка индексировать пустую строку — подобно попытке обратиться к нулю, что приведет также к ошибке:

var s: string; begin s:=»; s[1]:=’a’; // причина ошибки end;

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

procedure TForm1.Button1Click(Sender: TObject); var p1 : pointer; p2 : pointer; begin GetMem(p1, 128); GetMem(p2, 128); Move(p1, p2, 128); Move(p1^, p2^, 128); FreeMem(p1, 128); FreeMem(p2, 128); end;

Источник: thedelphi.ru

Диагностические сообщения компилятора Delphi

  • 0. expected but found. Обычно это сообщение возникает при синтаксической ошибке.Например,в случае небаланса скобок,компилятор сообщит: ‘)’ expected but ‘;’ found (вместо ожидавшейся скобки найдена запятая). Компилятор часто сообщает, что ‘end’ ожидается,например:x:= 5,7; здесь неуместен разделитель-запятая, а сообщается про end. (‘END’ expected but ‘,’ found)
  • 1. is not a type identifier. Данное не является именем типа.
  • 2. ‘;’ not allowed before ‘Else’. Перед else нельзя ставить точку с запятой
  • 3. Abstract method must be virtual or dynamic. Абстрактный метод должен быть виртуальным или динамическим.
  • 4. Ambiguous overloaded call to . Компилятор не может однозначно выбрать перегружаемый блок. Измените параметр.
  • 5. Array type required. Ошибка возникает в случаях, когда в индексе элемента массива указано больше уровней, чем предусмотрено описанием, и если массив не описан. Например, после объявления двумерного массива х или простой переменной х ошибочно записывают элемент х[2,1,1] (в нем показано три измерения).
  • 6. Assignment to FOR-loop variable . Присваивание значения параметру FOR-цикла в теле цикла. Например, вследствие описки дважды используется имя i в кратном цикле:

For i:= 1 to n do For i:= 1 to m do .

Рассмотрим также некоторые сообщения классов warning и hint.

  • Return value of function might be undefined. В теле функции нет присваивания ее результата.
  • Variable might not have been initialized. Указывает имя переменой, которой не задали значения.
  • For-Loop variable may be undefined after loop. Попытка использования значения параметра For-цикла после завершения этого цикла.
  • Text after final ‘END.’ ignored by compiler. Текст, идущий за конечной строкой модуля, игнорируется компилятором.
  • Variable is declared but never used in . Обращает внимание на переменную , описанную,но не нашедшую применения.
  • Value assigned to never used. Хотя бы одно значение переменной никак не использовано.

Несколько рекомендаций

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

Не удаляйте прежний вариант кода,пока не убедитесь,что ошибка устранена. Лучше на время закомментировать код,заключив его в скобки:

Компилятор не анализирует, как будет выполняться программа, поэтому выход значения индекса из диапазона выявляет только если индекс задан константным выражением. Деление на ноль вообще пропускается, кроме оператора div, в случае если делитель — константное выражение.

Источник: codenet.ru

Delphi — Работа над ошибками

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

Некоторые из рецептов моей кулинарной книги я хочу предложить на ваше суждение. Очевидность этих правил зависит от вашей квалификации. Согласие с ними зависит от вашей собственной системы. Их источником послужили мой личный опыт и опыт ошибок начинающих программистов, каждое поколение которых повторяет их, к сожалению, с завидной стабильностью .

  • Повышение надежности работы программы, т.е. уменьшение вероятности возникновения ошибки. Вероятность возникновения ошибки существует всегда, никто не безгрешен (включая операционную систему). Задача программиста — свести эту вероятность к минимуму.
  • Увеличение устойчивости программы — свойства, при котором она возвращается в стабильное состояние после возникновения возмущения (ошибки) (а не зависает, исчезает или уваливает операционную систему).
  • Написание единообразного и легко поддерживаемого кода.

Warnings and Hints

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

Правило:
Добивайтесь, что бы ваша программа компилировалась без предупреждений и намеков. Даже если они не существенны, в последствии в их массе вы или пользователи вашей библиотеки могут не заметить более важные предупреждения.

Используйте именованные константы. Это увеличивает «настраиваемость» исходного кода. А также избавляет от проблем связанных с изменением значения константы в случае ее множественного вхождения.

Range Check и Integer Overflow Check

К сожалению, эти опции компилятора по умолчанию отключены в Delphi, и многие разработчики не пользуются их услугами, а зря. Появления этих ошибок говорит о наличии в программе семантических ошибок, таких как неправильная индексация массива или использование несоответствующего целочисленного типа. Последствия этих ошибок могут быть весьма коварны. Я советую оставлять эти флаги всегда включенными, независимо от того — это отладочная или «финальная» версия программы. Лучше иметь неработающую программу (или ее часть), чем программу работающую неправильно (IMHO).

Читайте также:
Самая простая программа для сведения треков

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

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

  • Проверяйте значения переменных на допустимость. Особенно это касается переменных типа указатель, процедурных переменных и объектов.
  • Защищайте пары выделение-освобождение ресурсов блоками try/finally. Предполагайте, что исключение может произойти в любом операторе.
  • Используйте процедуру Assert для проверки условий, которые всегда должны быть истинными.

Объем кода, добавленный для проверок и обработки ошибок, может достигать порядка «полезного» кода! Но, такой стиль программирования является необходимым условием при написании сложных систем. Что поделаешь, из бревен небоскреб не построишь

Значения по умолчанию и «неопределенные» значения

В логике распределения значений для переменных всегда необходимо предусматривать «неопределенное» значение и значение по умолчанию. Отсутствие таких значений достаточно часто приводят к семантическим ошибкам.

  • Для указателей и объектов пустым значением должно являться значение nil.
  • Для числовых типов лучше всего резервировать значение ноль.
  • Для строковых переменных — пустая строка
  • Для перечислимых типов необходимо предусмотреть специальное значение.

Правило №2:
«Неопределенными» значениями лучше всего выбирать такие, чье двоичное представление соответствует нулю (нулям). Это увеличивает устойчивость, когда не выполнена начальная инициализация переменной, но произведена инициализация блока памяти, в котором она размещается.

Пример
Для перечислимых типов «неопределенное» значение должно быть первым, так как оно соответствует целочисленному нулю.

Инициализация переменных и полей

Неинициализированные переменные часто становятся причинами возникновения ошибок-фантомов. Обычно они имеют нерегулярную природу, и их трудно выявить в процессе отладки. Особенно катастрофичными могут быть последствия при таком обращении с указателями.

  • Для глобальных переменных: использовать типизированные константы, инициализированные переменные или присваивать начальные значения переменным в секции инициализации модуля.
  • Для локальных переменных: присваивать начальные значения в первых строках процедуры или функции.
  • Для полей объектов: присваивать начальные значения полям в конструкторе и не полагаться на то, что память, выделенная под объект, инициализируется нулями.
  • Массивы, записи и выделенные блоки памяти очень удобно инициализировать при помощи функции FillChar. Но, с появлением в Delphi «управляемых» (manageable) типов (длинные строки, динамические массивы, варианты и интерфейсы), пользоваться ей необходимо с четким пониманием.

type TStrArray = array[1..10] of string; var A: TStrArray; . FillChar(A, SizeOf(A), 0);

В данном примере вызов процедуры FillChar проинициализирует строки пустыми значениями, такой подход был нормальным в ранних версиях Delphi и Borland Pascal, но не допустим в последних версиях, в которых тип string по умолчанию соответствует типу LongString и суть указатель. Если значения строк перед инициализацией были не пусты, то мы получим утечку памяти.

В Delphi параметры функций и процедур по умолчанию передаются по значению. Т.е. для них выделяется область памяти в стеке или куче, куда копируются оригинальные значения. При передаче параметров сложных типов (запись, массив, строка, вариант) это сопряжено со значительными расходами ресурсов, поэтому параметры этих типов желательно передавать по ссылке, т.е. с использованием ключевых слов var или const. Замечено, что наиболее типична эта ошибка при передаче параметра типа string.

procedure Proc(s: string); //Не очень хорошо 🙁 procedure Proc(const s: string); //Гораздо лучше 🙂
Функции, процедуры и состояния

Для начала словарь терминов: Функция — это подпрограмма, задачей которой является получение (извлечение, вычисление и т.д.) определенного значения на основании входных параметров и текущего состояния системы. Процедура — это подпрограмма, которая предназначена для выполнения каких либо действий над системой, и соответственно изменяет состояние системы. Просьба не путать эти определения с ключевыми словами function и procedure.

Правило:
Подпрограмма должна быть либо функцией, либо процедурой. Не совмещайте эти две задачи в одной подпрограмме, разделите ее на несколько подпрограмм.

Контроль достижения предела

Довольно часто встречаются случаи, когда контроль достижения предела цикла осуществляется условием равенства.
Пример

repeat . Inc(I); until I = Limit;

Что произойдет, если в результате ошибки (или просто модификации алгоритма) переменная I перескочит через значение Limit? Правильно — ничего хорошего. Более устойчивой будет конструкция с использованием условия отсечения диапазона, т.е. I >= Limit.

Частота выделения-освобождения ресурсов

Очевидно, что скорость потери ресурсов (памяти, дескрипторов и т.д.) пропорциональна частоте их выделения. Рассмотрите варианты реализации, в которых ресурсы выделяются наиболее редко. Таким образом, вы сможете отсрочить крах программы, и некоторые пользователи могут даже и не узнать, что с ней что-то не так.

Пример:
Допустим, в объекте есть метод DoSomething. В процессе работы он выделяет и освобождает память, которая нужна только ему. С точки зрения «выделения ресурсов по месту их использования» — все корректно, но при многократном обращении к этому методу и в случае наличия ошибки при освобождении памяти вы можете получить достаточно интенсивную утечку памяти. В данной ситуации имеет смысл рассмотреть одноразовое выделение памяти при создании объекта и освобождении при его разрушении. В данной ситуации при наличии ошибки скорость утечки будет гораздо меньше. Естественно, что данное решение необходимо рассматривать в комплексе с другими задачами (производительность, минимизация расхода ресурсов и т.д.)

Область использования переменных

  • Объявляйте переменные по месту их использования.
  • Избегайте использования глобальных переменных. Если все же без них не обходиться, то не забывайте, что ваша библиотека может быть использована в многопотоковом приложении.
Читайте также:
Самые популярные российские программы

Четко специфицируйте, какие методы, свойства и поля могут быть доступны и каким образом. «Прячьте» методы, свойства и поля, которые не должны быть доступны извне. Не давайте возможность пользоваться «недокументированными» возможностями ваших объектов. Если по каким либо причинам скрыть эти элементы не получается (к сожалению, система прав доступа к элементам объекта в Delphi не совершенна), тогда не забудьте оформить соответствующий комментарий.

«Просачивание» исключений в библиотеках

При написании библиотеки функций или классов не закрывайте просачивание исключений наружу, если это конечно не предусмотрено логикой библиотеки.

try . exception //Очень нехорошо on Exception do ShowMessage(‘Something wrong’ s happened: — (»); end;

Обработка исключений, возникших в библиотеке — это задача приложения, которое использует данную библиотеку.

Определение и использование классов

  • определения — в которой определяются переменные, функции, классы, их методы и т.д.
  • использования — создание экземпляров классов в секции инициализации модуля.

Правило:
При планировании библиотеки классов не совмещайте в одном модуле части определения и использования. Или другими словами — отделяйте определение класса от того, как он будет использован.

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

Линковщик не может определить, какие виртуальные методы будут вызваны, так как теоретически все они могут быть вызваны косвенно. По этому достаточно одного «упоминания» класса, как весь код его виртуальных методов (а также виртуальных методов других классов, на которые он ссылается) будет влинкован в ваше приложение, тут же. Во избежание подобной проблемы модуль Forms надо было бы разделить на две части: в одной — только определения, а в другой — создания экземпляров, выше указанных, глобальных переменных.

Я столкнулся с описанной проблемой при написании серверного приложения без GUI, которое взаимодействует с базой данных. Где-то в недрах DBxxx компонент есть ссылка на модуль Forms. Эта «особенность» была замечена в Delphi 5, скорее всего эта проблема имела место и в предыдущих версиях. Справедливости ради надо отметить, что в Delphi 7 эта особенность устранена.

Циклические ссылки модулей и «осведомленность» сущностей

Технически, Object Pascal позволят создать циклические ссылки между модулями. Их наличие в программе или библиотеке свидетельствует о не очень удачной декомпозиции (IMHO). Негативными последствиями их использования есть:

  • Вероятность, что в ваш проект будут включены неиспользуемые модули;
  • Сложная логика взаимодействия компонент, что усложняет поддержку и развитие проекта, а так же может быть источником функциональных ошибок.

Правило:
Избегайте использования циклических ссылок модулей. Старайтесь организовать «осведомленность» сущностей древовидной (сущности верхнего уровня знают о существовании сущностей нижнего уровня, но не наоборот). Обратное взаимодействие можно реализовывать посредством механизма событий (процедурных переменных) или при помощи сущностей «посредников».

Предположим, мы разрабатываем приложение, в котором должны производиться некоторые вычисления и процесс этих вычислений, должен представляться пользователю. Грубо говоря, в данной ситуации мы имеем две сущности: «интерфейс пользователя» и «вычислительный механизм». Они оба должны взаимодействовать друг с другом: «интерфейс пользователя» должен настраивать «вычислитель» и запускать его на выполнение, а «вычислитель» должен выдавать информацию о ходе расчетов. Можно предложить следующий вариант решения:

Модули интерфейса пользователя и вычислителя работают непосредственно друг с другом, т.е. «интерфейс пользователя» вызываем методы «вычислителя» и наоборот. Все будет работать великолепно, пока не окажется, что «вычислитель» необходимо использовать в другой задаче с другим интерфейсом пользователя (или без оного вообще). Обойти данную проблему можно, если в «вычислителе» задачу общения с «внешним миром» (в данном случае — интерфейс пользователя) возложить на функции обратного вызова (callback functions). При таком подходе, заинтересованная сторона регистрируется у «вычислителя», и он будет вызывать ее функции, не подозревая, с кем имеет дело.

Анализ:
В первом случае мы имели двунаправленную «осведомленность» сущностей друг о друге, что привело к проблемам с повторным использованием кода «вычислителя». Во втором случае у нас только однонаправленная «осведомленность» сущностей, т.е. «интерфейс пользователя» знает о вычислителе, но не наоборот. Если необходимо повторно использовать код «интерфейса пользователя», можно пойти дальше — сущность «приложение» знает о существовании сущностей «интерфейс пользователя» и «вычислитель», но последние ничего не знают друг о друге и взаимодействуют через сущность «приложение», исполняющую роль посредника.

Исключения в обработчике события OnTimer

При написания обработчика события OnTimer компонента TTimer необходимо учитывать, что возникновение исключения в нем для обычного Delphi приложения без специализированной обработки исключений приведет к выскакиванию диалога с сообщением об ошибке. Но это не останавливает работу таймера. И если причина возникновения исключения устойчива, то скоро вы увидите следующее сообщения и т.д., пока у системы не закончатся какие-нибудь ресурсы.

    Обрабатывать исключения непосредственно в обработчике события

try except on E: Exception do Application.ShowException(E); end;
Application.OnException := MyExceptionHandler;

Последний подход желательно использовать в серверных приложениях, когда нет пользователя, который интерактивно взаимодействует с приложением.

Правила приведенные в этой статье носят общий характер. Практически всегда существуют исключения (такова природа правил J). Следование этим правилам, позволило мне добиться разработки устойчивого и единообразного кода. Буду признательным за любые дополнения, исправления, замечания, примечания, пожелания и критику (особенно конструктивную).

Статья Delphi — Работа над ошибками раздела Синтаксис Ошибки и Исключения может быть полезна для разработчиков на Delphi и FreePascal.

Комментарии и вопросы

Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.

Источник: www.kansoftware.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru