Миф №13: освобождение памяти уменьшает показатели использования памяти программы
Многие ожидают, что освобождая память, вы возвращаете её системе. И снова это выглядит логично, но что будет на практике?
Создадим пустое приложение с двумя кнопками и Edit-ом: первая кнопка будет выделять память, указанную в Edit-е, а вторая - её освобождать:
1
2
3
4
5
6
7
8
9 |
procedure TForm1.Button1Click(Sender: TObject);
begin
Tag := Integer(AllocMem(StrToInt(Edit1.Text) * 1024));
end;
procedure TForm21.Button2Click(Sender: TObject);
begin
FreeMem(Pointer(Tag));
end; |
Запустим программу и попробуем щёлкать на кнопках со значением 10240 (10 Мб).
Ну, при выделении памяти потребление виртуальной памяти приложением подскакивает на +10 Мб, а при освобождении - уменьшается.
Миф подтверждён? Мы так легко не сдаёмся: попробуйте повторить этот же эксперимент, указывая значения вроде 1 или 4. Теперь вы можете заметить, что при освобождении памяти, занятая виртуальная память не изменяется. Более того, если вам достаточно повезёт, то вы увидите, что при выделении памяти, потребление виртуальной памяти не увеличивается!
(примечание: это плавающее поведение; возможно, вам придётся поэкспериментировать с выделением/освобождением памяти, прежде чем вы его воспроизведёте)
Неужели мы открыли неизвестный науке принцип возникновения памяти из ничего? Вовсе нет - вспомните, что работа с памятью в Delphi (да и других языках тоже) идёт через менеджер памяти - это прослойка между вами и системой, которая, грубо говоря, упаковывает ваши запросы на память в один пакет. В предыдущем мифе мы уже увидели, что при выделении всего 1 Кб памяти на деле расходуется в несколько раз больше памяти - из-за её гранулярности. Чтобы память не пропадала зря, менеджер памяти располагает в одном блоке памяти сразу несколько ваших запросов на память - вот почему потребление памяти может не изменяться при выделении/освобождении памяти: потому что память будет "выделена" в уже существующем блоке памяти, либо же при освобождении памяти менеджер памяти не сможет освободить блок памяти, потому что там есть и другие занятые регионы (либо он может просто придержать свободный блок, на случай, если вы сейчас захотите заново выделить память).
Заметьте, что это не является какой-то "плохой" вещью, как вам может показаться. Мы уже разрушили такой миф: вспомните, что потребление оперативной памяти программы крайне слабо связано с выделением памяти в ней. Вы можете выделить 1 Гб памяти, но в оперативной памяти система даст вам всего 2 Мб. Так и с этой, временно не используемой памятью: она никак не мешается и лежит в файле подкачки, пока вам не понадобится или пока её не освободят.
Конечно же, сказанное не означает утечку памяти - вся эта память будет в итоге возвращена системе. Либо с течением времени, либо при последующих освобождениях памяти. Так что в целом, на длительном участке, миф выполняется: освобождение памяти в вашем коде уменьшает потребление памяти программой, но это может быть не так локально, на достаточно малых участках кода.
Статус мифа: plausible.
Примечание: предыдущие два мифа приводят к неизбежному заключению: вы не можете судить о том, валиден (т.е. допустим, корректен) ли данный вам указатель (т.е. была ли под него выделена память) или нет. Более того, это нельзя сделать даже при отсутствии факторов, про которые говорится в мифах:
1
2
3 |
P := AllocMem(1024);
FreeMem(P);
N := AllocMem(1024); |
Валиден ли P? Логически - нет, т.к. у нас есть явное освобождение памяти. Но любая проверка покажет вам, что он допустим: потому что память под N выделится ровно на том же самом месте, где были данные от P. Таким образом, P и N будут указывать на одно и то же место в памяти, даже хотя P более не считается допустимым по этому коду. Поскольку вы не можете гарантировать неосуществимость этой операции на практике (за исключением специальных отладочных сборок, конечно же), то вы и не можете судить о допустимости указателя, основываясь на любых его проверках: какую-бы проверку вы ни предложили бы, всегда найдётся ситуация, когда проверка будет давать ложно-положительный результат.
Поэтому, когда вы освобождаете память, всегда присваивайте указателю nil: тогда его проверка на допустимость будет тривиальной if Assigned(P) then.