работа или точнее взаимодействие с памятью

Предыдущая тема Следующая тема Перейти вниз

работа или точнее взаимодействие с памятью

Сообщение  Eric-S в Чт Мар 11, 2010 5:19 pm

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

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


И так, память! В смысле компьютерная оперативная память. Про диски, регистры и подобное говорить не будем!

На современных машинах используется иерархическая структура.

Есть память физическая, это те самые планки , на 512 MB или на 1024 MB.
Но есть и подкачка, в комплексе это даёт виртуальную память. Она управляется спецальным программно-аппаратным комплексом - менеджером виртуальной памяти.

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

Когда запрашивается память, менеджер выделяет страницу, она размером около 16 KB.
Если страница долго не используется, то она записывается на диск, в файл подкачки.
Если же нужная страница в файле подкачки, то менеджер загружает её в память.
При этом менеджер оперирует виртуальными адресами. А в ссылках, используется номер страницы. Реальный же адрес в физической памяти или в файле подкачки, заносится в таблицу адресов.

Указатель состоит из двух чисел. Номера страницы и смещения в этой странице.

Вообщем-то файл подкачки, да и вообще работа с памятью, производится на ещё более низком уровне. За это отвечает специальное прерывание процессора. А системные функции и менеджер виртуальной памяти, это обёртка, над низкоуровневвыми функциями ядра системы.

Виртуальная память, на прямую не удобна. Страницы большие и просто не экономично тратить их на отдельные переменные.

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

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

Переменные, обычно создаются в стеке. Том самом, который управляется ассемблерными командами push и pop. Вообщем-то это тоже страницы виртуальной памяти, но они заранее выделяются и экономно расходуются. Обычно у программы стек размером 1 MB. Но это значение может быть изменено при компиляции, или при выполнении, может быть создан и подключен дополнительный стек.
Но на счёт стека, не стоит задумыватся.

Хотя... С одной оговоркой.
Как я уже сказал, переменные в функциях, и даже массивы, а так же переменные которые передаются в функцию, они размещаются в стеке.
По этому учитывайте тот факт, что стек не резиновый. Особенно если вы уходите в глубокую рекурсию.
В частности, это касается функций WinProc, которые являются функциями обратного вызова.

А у программ, есть ещё одна память. Это куча (heap). У кучи, есть свой менеджер памяти, своя таблица размещений.

Когда куча создаётся, функцией HeapCreate, выделяется нужное количество виртуальной памяти.

Далее, из этой кучи, можно запросить нужный кусок, с помощю функции HeapAlloc.

В таблице помечается фрагмент памяти, как занятый, а функция возвращает указатель, на начало этой памяти.

Когда память освобождается, функцией HeapFree, нужный кусок помечается как незанятый.

В этой схеме есть сложности.

Во-первых, если память резервируется, то она должна быть освобождена.

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

Зарезервированную память нужно освобождать только один раз. Повторное освобождение, может привести к серьёзным последствиям - потеря информации или сбой программы.

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

При работе с указателем на блок памяти, нужно использовать только зарезервированный объём.
Если было запрошено 255 байт, то доступ к 256 будет уже ошибочным, хотя компилятор это позволит. Опять же, это остаётся на совести программиста.

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

У приложения может быть одна или более куч. Хэндл кучи по умолчанию, можно узнать с помощью функции GetProcessHeap.
Её не нужно создавать.

Если вы создали дополнительную кучу, то по окончанию, её нужно освободить функцией HeapDestroy.


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

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


Функции allocate, deallocate, reallocate, new, delete, это функции, которые работают с внутренним менеджером памяти.

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

Но кроме всего прочего, функции, встроенные в язык, намного проще в использовании, чем системные API.


И так, коротко разберём функции:

allocate - резервирует блок памяти. В этом блоке могут быть старые данные, т.н. мусор.

callocate - резервирует блок памяти и обнуляет память. На обнуление тратятся дополнительные ресурсы, но теперь память будет заполнена двоичными нулями.

reallocate - изменяет размер зарезервированной памяти.

deallocate - освобождает блок зарезервированной памяти.


Эти функции работают с указателями произвольного типа (any ptr). Что чревато некоторыми ошибками. Язык, в стандартном режиме, не выдаёт ошибок, но я рекомендую, приводить указатель к определённому типу.

Код:

dim len as integer = 255
dim ws as wstring ptr
ws = cast( wstring ptr, allocate(
 sizeof( zstring ) * len ))
...
deallocate( ws )


Альтернативой функций allocate и deallocate, стали операторы new[] и delete[]. Вот тот же код.

Код:

dim len as integer = 255
dim ws as wstring ptr
ws = new wstring[255]
...
delete[] ws

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

Я рекомендую использовать именно такой способ. Он более типобезопасен, более конкретен.

Функции allocate и deallocate, можно было бы отправить в корзину, но у них остаётся несколько плюсов. Они работают немного быстрее. Но при этом они требуют, от программиста, большей внимательности.

В отличии от оператора new[] и delete[], операторы new и delete выполняют другую роль. Кроме резервирования памяти, они запускают конструктор и деструктор.

Вот можем посмотреть такую програмку:
Код:


type someclass
declare constructor()
declare destructor()
a as integer = 111
b as integer
d as integer = 333
end type

someclass.constructor()
print "constructor"
this.b = 222
end constructor

someclass.destructor()
print "destructor"
end destructor

Я описал некий примитивный класс. На самом деле, числа 111 и 333, присваиваются, в момент создания объекта. Компилятор добавляет ещё один скрытый конструктор.

И так, вот первый тест:
Код:

dim obj1 as someclass ptr
obj1 = cast( someclass ptr, allocate( sizeof( someclass )))
print obj1->a
print obj1->b
print obj1->d
deallocate obj1

Что он вам выведет, я не гарантирую. Как я уже сказал, функция allocate память не очищает и в ней лежит мусор. Вот именно этот мусор, и будет выведен.
Хотя, программа, логически верна. Если конечно не считать, что конструктор и деструктор не запустятся.

Но в принципе, если вам нужна некая структура, то это, более-менее нормально.

Вот к примеру так:

Код:


type someclass2
a as integer
b as integer
d as integer
end type

dim obj1 as someclass2 ptr
obj1 = cast( someclass2 ptr, allocate( sizeof( someclass2 )))

obj1->a = 111
obj1->b = 222
obj1->d = 666

print obj1->a
print obj1->b
print obj1->d

deallocate obj1

Мы уже получили объект структуры. К которому можем обращатся и работать с ним.

Гэ-ха-эм. Вот тут, был поднят вопрос о строках (string) в качестве поля такой структуры.
По идеи, это конечно можно. И компилятору не на что ругатся. Но из-за того, что string не является простым типом, у него есть дескриптор, который нужно инициализировать... То выйдет облом. Точно такой же, как и с другими пользовательскими типами.

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

И так, вот пример:
Код:

dim obj2 as someclass ptr
obj2 = new someclass()
print obj2->a
print obj2->b
print obj2->d
delete obj2

И получим, то что нам было нужно

constructor
111
222
333
destructor


Вот! Надеюсь, эта информация будет вам полезна!
Жду ваших вопросов!

Eric-S

Сообщения : 738
Дата регистрации : 2008-08-06
Возраст : 34
Откуда : Россия, Санкт-Петербург

Посмотреть профиль http://eric50.narod.ru

Вернуться к началу Перейти вниз

Предыдущая тема Следующая тема Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения