При работе с интерфейсами в Delphi, особенно в контексте старых проектов, иногда возникают неожиданные ошибки. Одна из таких ошибок связана с неправильным выделением памяти под интерфейс. В данной статье мы рассмотрим, почему для корректной работы с интерфейсами в Object Pascal необходимо выделить память большего размера, чем SizeOf(ITest).
Описание проблемы
Разработчик столкнулся с ошибкой доступа (Access Violation) при выполнении следующего кода:
program Project65;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils;
type
ITest = interface
end;
TTest = class(TInterfacedObject, ITest)
end;
var
p: ^ITest;
begin
GetMem(p, SizeOf(ITest)); // Выделяем память под интерфейс
p^ := TTest.Create; // Ошибка доступа здесь
try
finally
p^ := nil;
FreeMem(p);
end;
end.
Автор кода знает, что интерфейсы должны использоваться иначе, но работает с наследуемым кодом, где применяется данный подход. Он был удивлён, обнаружив, что выделения памяти размером в SizeOf(ITest) недостаточно для корректной работы с интерфейсом ITest.
Интересно, что если изменить размер выделенной памяти на 21 байт, ошибка доступа исчезает. Если же выделить менее 20 байт, ошибка сохраняется.
Возможные причины и решение
При выделении памяти с помощью функции GetMem, она не гарантирует, что выделенные байты будут инициализированы нулями. Если интерфейс уже содержит данные, RTL может попытаться вызвать метод _Release у несуществующего объекта, что приведёт к ошибке доступа. Необходимо обнулить память перед созданием объекта:
program Project65;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils;
type
ITest = interface
end;
TTest = class(TInterfacedObject, ITest)
end;
var
p: ^ITest;
begin
GetMem(p, SizeOf(ITest));
FillChar(p^, SizeOf(ITest), #0); // Обнуляем память
p^ := TTest.Create; // Теперь ошибка доступа отсутствует
try
// ...
finally
p^ := nil;
FreeMem(p);
end;
end.
Альтернативные подходы
Вместо использования GetMem и FreeMem, можно использовать AllocMem, который автоматически инициализирует память нулями. Это упрощает код и устраняет необходимость вручную обнулять память:
program Project65;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils;
type
ITest = interface
end;
TTest = class(TInterfacedObject, ITest)
end;
var
p: ^ITest;
begin
p := AllocMem(SizeOf(ITest)); // Выделяем и инициализируем память нулями
p^ := TTest.Create; // Теперь ошибка доступа отсутствует
try
// ...
finally
FreeMem(p, SizeOf(ITest));
end;
end.
Заключение
При работе с интерфейсами в Delphi важно правильно выделять память и обнулять её перед использованием. Это предотвращает непредвиденные ошибки доступа и гарантирует корректное функционирование интерфейсов в контексте управления памятью. Использование AllocMem может быть предпочтительнее, так как оно автоматически инициализирует память нулями, что упрощает код и делает его более надёжным.
Разбираем проблему, связанную с неправильным выделением памяти под интерфейс в Delphi, когда необходимо выделить на 21 байт больше, чем `SizeOf`, чтобы избежать ошибки доступа.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS