Почему я теряю память в EOutOfResources? - PullRequest
2 голосов
/ 13 марта 2020

Я пытаюсь реализовать обертку вокруг этой библиотеки декодера jpeg (оригинал Арно Буше). Библиотека работает быстро, но она не поддерживает все jpegs!

Для очень больших файлов jpg происходит сбой (как и ожидалось) с исключением EOutOfResources.
Поэтому я пытаюсь пропустить эти файлы без вывода сообщений. Это работает, но когда я закрываю приложение, FastMM указывает на утечку памяти.

function FastJpgDecode(FileName: string; OUT ErrorType: string): TBitmap;
var Img: PJpegDecode;
    res: TJpegDecodeError;
    Stream: TMemoryStream;
begin
  Result:= NIL;
  Stream:= TMemoryStream.Create;
  TRY
    if Length(FileName) > MAX_PATH then { TMemoryStream does not support long paths }
     begin
      ErrorType:= 'File name too long!';
      Exit;
     end;

    Stream.LoadFromFile(FileName);
    Stream.Position:= 0;
    res:= JpegDecode(Stream.Memory, Stream.Size, Img);       
    case res of
     JPEG_SUCCESS:
      begin
       try
        Result:= Img.ToBitmap; // This will raise an EOutOfResources for large files!
       except
        on EOutOfResources do
          ErrorType:= 'JPEG_OUTOFMEM!';
       end;
      end;

     JPEG_EOF                : ErrorType:= 'JPEG_EOF!';
     JPEG_OUTOFMEM           : ErrorType:= 'JPEG_OUTOFMEM!';
     JPEG_CPUNOTSUPPORTED    : ErrorType:= 'JPEG_CPUNOTSUPPORTED!';
     JPEG_BADFILE            : ErrorType:= 'JPEG_BADFILE!';
     JPEG_FORMATNOTSUPPORTED : ErrorType:= 'JPEG_FORMATNOTSUPPORTED!';       // Not all jpegs are supported. In this case we fall back to WIC or the standard LoadGraph loader (WIC).   
    end;
  FINALLY
    Img.Free;
    Stream.Free;
  END;
end;



function TJpegDecode.ToBitmap: TBitmap;
begin
  if @self=nil
  then result := nil
  else
   begin
    result := TBitmap.Create;
    try
     if not ToBitmap(result)   // This will raise an EOutOfResources for large files!
     then FreeAndNil(result);
    except
      FreeAndNil(Result);
      raise;
    end;
   end;
end;

Утечка блока памяти. Размер: 36

Этот блок был выделен потоком 0xD0 C, и трассировка стека (адреса возврата) в то время была: 407246 40830F 408ADE 43231B [Неизвестная функция в __dbk_fcall_wrapper] 407246 40A532 53C353 [Неизвестно функция в TMethodImplementationIntercept] 6E006F [Неизвестная функция в TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError] 77656494 [RtlNtStatusToDosError] 767A7BEA [в настоящее время используется неизвестный объект *1010* 2 * *, в настоящее время используется только несколько объектов: 1010 * * не используется;

Текущий дамп памяти из 256 байтов, начиная с адреса указателя 7EEEA6C0: 74 7F ............ t D. ü $ ú ......

Утечка в блоке памяти. Размер: 132
Этот блок был выделен потоком 0xD0 C, и трассировка стека (адреса возврата) в то время была: 407246 40A2E7 40A518 53C341 [Неизвестная функция в TMethodImplementationIntercept] 6E006F [Неизвестная функция в TMethodImplementationIntercept] 7765648 [RtlNtStatusToDosError] 77656494 [RtlNtStatusToDosError] 767A7BEA [Неизвестная функция в IsNLSDefinedString] 7677F0BA [VirtualQueryEx] 7677F177 [VirtualQuery] 898FD9 [объект GetFrame * 22 * ​​* * 10 * * 10 * используется * 10 * используется * класс: 10 * используется * класс: 0 Номер выделения: 4180

Текущий дамп памяти 256 байтов, начиная с адреса указателя 7EFA24F0: B0 04 02 00 01 00 00 00 .......... , , , , , , : , , Нет , достаточно места для хранения , я с. , а. против а. я л а. б) л эл. , т. о , п . р . о c. эл. с. с. , т. ч. я с. , c. о м м а. п. d ............

В этом приложении произошла утечка памяти. Утечки небольшого блока (исключая ожидаемые утечки, зарегистрированные указателем):

21 - 36 байт: EOutOfResources x 1 117 - 132 байт: UnicodeString x 1

Почему происходит утечка памяти есть

1 Ответ

5 голосов
/ 13 марта 2020

Как уже упоминалось в комментариях @kami, EHeapException имеет внутренний флаг AllowFree, который по умолчанию имеет значение False, предотвращая освобождение экземпляров EHeapException обработчиками исключений.

EOutOfResources происходит от EOutOfMemory, что, в свою очередь, происходит от EHeapException.

В блоке SysUtils есть 2 одноэлементных объекта типа EOutOfMemory и EInvalidPointer. Всякий раз, когда RTL напрямую вызывает эти два указанных c типа исключения, он каждый раз вызывает один и тот же экземпляр этих классов. Поэтому у них есть флаг AllowFree, чтобы обработчики исключений не освобождали синглтоны. Синглтоны освобождаются, когда устройство SysUtils завершено.

Это фактически задокументированное поведение:

http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EHeapException

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

http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EOutOfMemory

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

Примечание. Никогда не повышайте EOutOfMemory напрямую. Вместо этого вызовите глобальную OutOfMemoryError процедуру.

Однако, хотя EOutOfResources является производным от EHeapException, он никогда не используется однозначно, поэтому его флаг AllowFree действительно никогда не должен быть Ложь. Поэтому мне кажется, что здесь есть несколько ошибок:

  • EOutOfResources на самом деле не является кучей ошибок и не должны были быть получены из EHeapException с самого начала. Это на самом деле распространенное исключение, например, модуль Vcl.Graphics вызывает EOutOfResources для некоторых из своих ошибок GDI, которые не имеют ничего общего с кучей.

  • EOutOfResources имеет флаг AllowFree установлен в False, когда вместо него должно быть значение True. И флаг private, поэтому он не может быть перезаписан кроме как блоком SysUtils, который делает это только для 2 синглетонов во время финализации. Таким образом,

  • синглтоны, как и все другие экземпляры-потомки, не передаются в RTL. RegisterExpectedMemoryLeak() функция, когда AllowFree имеет значение False, поэтому их можно исключить из отчетов об утечках.

Эта проблема утечки существует с Delphi 5 и уже была сообщена Embarcadero:

RSP-17193: утечка памяти EOutOfResources

RSP-19737: исключение EOutOfResources вызывает утечку памяти

...