Marshal.AllocHGlobal (0) - Почему это не возвращает IntPtr.Zero - PullRequest
0 голосов
/ 29 августа 2018

Просто пытаюсь понять, имеет ли это какой-то смысл и где смысл в этом может быть.

Marshal.AllocHGlobal(int cb) выделяет указанное количество байтов в неуправляемой памяти.

Но почему Marshal.AllocHGlobal(0) на самом деле возвращает IntPtr, то есть , а не IntPtr.Zero? И я должен освободить выделенные 0 байтов, когда я закончу с использованием 0 байтов?

Я не вижу логики этой реализации, кто-то может объяснить это?

Ответы [ 2 ]

0 голосов
/ 29 августа 2018

1. Почему Marshal.AllocHGlobal не возвращает IntPtr.Zero, если выделено 0 байтов?

Marshal.AllocHGlobal внутренне вызывает функцию WinAPI LocalAlloc с WinBase.h.

Почему Marshal.AllocHGlobal(0) не возвращает IntPtr.Zero: LocalAlloc возвращает только NULL (эквивалент C #: IntPtr.Zero) в случае сбоя во время выделения. Это также можно увидеть в исходном коде :

IntPtr pNewMem = Win32Native.LocalAlloc_NoSafeHandle(LMEM_FIXED, unchecked(numBytes));

if (pNewMem == IntPtr.Zero) {
    throw new OutOfMemoryException();
}
return pNewMem;

2. Почему выделение 0 байтов возвращает (действительный) адрес памяти?

В документации говорится о возвращаемом значении LocalAlloc:

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

В случае сбоя функции возвращается значение NULL.

Теперь LocalAlloc терпит неудачу, только если ‡ uBytes отрицательно ; у него нет проблем с положительными или нулевыми значениями.

Это означает, что распределение всегда будет успешным ‡, и вы всегда получите правильный указатель, если попытаетесь выделить 0 байтов.

‡ Существуют и другие причины неудачи, например, недостаточно памяти. Для простоты они были опущены в этом объяснении.


3. Должен ли я освободить память, выделенную на Marshal.AllocHGlobal(0)?

Подпись LocalAlloc такова:

DECLSPEC_ALLOCATOR HLOCAL LocalAlloc(
  UINT   uFlags,
  SIZE_T uBytes
);

Документация гласит, что

если [uBytes] равно нулю, а параметр uFlags указывает LMEM_MOVEABLE, функция возвращает дескриптор объекта памяти, помеченного как отброшенный.

По какой-то причине Marshal.AllocHGlobal(0) делает не pass LMEM_MOVEABLE, а скорее LMEM_FIXED вместо

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

IntPtr zeroBytesPtr = Marshal.AllocHGlobal(0);

// Do stuff with the pointer.

Marshal.FreeHGlobal(zeroBytesPtr);

Если бы Marshal.AllocHGlobal прошло LMEM_MOVEABLE вместо этого, не было бы необходимости нигде освобождать указатель.


Что касается тестов:

while(true) {
    void* v = LocalAlloc(LMEM_FIXED, 0);
}

выделяет память для каждой итерации цикла и каждый раз возвращает новый адрес, тогда как

while(true) {
    void* v = LocalAlloc(LMEM_MOVEABLE, 0);
}

выделяет память только один раз и возвращает один и тот же адрес каждый раз .

Это указывает, почему память, выделенная Marshal.AllocHGlobal, должна быть освобождена (поскольку она использует LMEM_FIXED), потому что новый объект памяти выделяется при каждом вызове.

0 голосов
/ 29 августа 2018

Для некоторых случаев использования может быть важно, чтобы два разных вызова AllocHGlobal никогда не возвращали одинаковое IntPtr значение (при отсутствии вызовов FreeHGlobal), даже если два вызова происходят с укажите несколько глупое значение размера.

В конце концов, вы вызываете эту функцию, предположительно, для взаимодействия с неуправляемым кодом, который ожидает работать с «глобальной» кучей. И GlobalAlloc уже давно заявлено, что он принимает значение 0, и функция всегда фактически выполняет некоторое распределение (если оно успешно).

...