Глава 3. Работа с паметта

Съдържание

Използване на статичната и стекова памет
„Кръговрат“ на данните при обектноориентираните езици
По-прости начини за работа с динамична памет при Си и Си++
I начин — притежателни и непритежателни указатели
II начин — пулове от непритежавани обекти
III начин — пул с броячи
IV начин — брояч за всеки от обектите
Технология, използвана при ОупънСтеп и МакОС X
Използването на събирач на боклука

При програмиране данните може да се разполагат в следните видове памет:

Статична памет

Променливите, които използват статична памет, се намират винаги на едно и също място в паметта.[10] При програмиране на Си такава памет използват глобалните променливи, както и променливите, обявени като static. Дори тези променливи и да не са видими от всички части на програмата, те запазват стойността си по време на цялото изпълнение на програмата.

Стекова памет

Такъв тип памет използват онези от променливите, дефинирани в телата на функции и методи, които не са обявени като static. Грубо казано можем да си мислим, че този тип променливи започват съществуването си при започване изпълнението на блока, в който са декларирани, и престават да съществуват при напускане на този блок.

Динамична памет

Този тип памет се заделя динамично в процеса на изпълнение на програмата при изпълнение на нарочни команди с това предназначение. При Си това става с библиотечните функции malloc и подобните й, а при Си++ може да се използва и операторът new. Понякога програмистът трябва да има грижата да освобождава така заетата памет, след като тя престане да бъде необходима. В други случаи това става автоматично с помощта на специален процес, наречен „събирач на боклук“.

Външна памет

Тя по принцип се намира не в оперативната памет, а във файлове. Освен традиционните функции за отваряне на файл, позициониране в него, четене на определен брой байтове и последващо затваряне, съвременните операционни системи предлагат функции, с помощта на които можем да направим всеки файл част от виртуалното адресно пространство без това да е свързано с голям брой входноизходни операции. По такъв начин можем да работим с външната памет така, сякаш тя е част от оперативната памет. При работа със сложни или обемни масиви от данни може да се използват и специализирани системи за управление на бази данни.

Използване на статичната и стекова памет

Когато ключовата дума static е употребена при обявяването на променлива извън дефиниция на функция и клас, тогава тя показва, че променливата ще бъде видима само в рамките на файла, в който тя е обявена. Типът памет, който тя използва обаче, е винаги статичен — променливата съществува по време на цялото изпълнение на програмата. В такъв случай възниква въпросът има ли смисъл да определяма дадена променлива като static, не се ли постига същият ефект, ако обявим променливата като глобална, но не я декларираме в никой от заглавните файлове (.h) — в такъв случай тя пак няма да е видима извън файла, в който е обявена.

Отговорът на този въпрос е следният. По време на компилация наистина няма разлика между това дали ще обявим дадена променлива като static или като глобална, но без да я декларираме в заглавните файлове. При свързване на отделните модули на програмата обаче, разликата ще се прояви. В два файла спокойно може да присъстват едноименни променливи от тип static, едноименни глобални променливи обаче не са допустими.

Има и още една разлика между двата типа променливи. Както вече видяхме, особеностите на езика Си пречат на съвременните компилатори да оптимизират добре кода при използване на глобални променливи; такива променливи почти никога не се намират в регистрите на процесора. Оптимизирането на кода при използване на променливи от тип static обаче е много по-успешно. Така че ако ни се налага да използваме данни, които се намират в статичната памет, а не в стекова или динамична, си струва да преценим дали не е по-добре да дефинираме променливата като static. В случай, че се налага достъп до нея от части на програмата, които се намират извън файла, в който тя е дефинирана, за целта може да използваме нарочни функции.

Когато ключовата дума static е употребена при дефинирането на променлива в рамките на тялото на функция или метод, тогава тя има съвсем друго значение — тя показва, че съответната променлива се намира в глобалната памет, макар че е видима само в рамките на нейната функция или метод. Такива променливи запазват стойността си между две извиквания на функцията. Без static такива променливи биха се намирали в стековата памет.

Чистият обектно ориентиран стил на програмиране не допуска използването на променливи static в методите.[11] Дори и да не се програмира напълно обектно-ориентирано използването на такива променливи води до нереентрантен код, т.е. до функции, които могат да се извикват повторно, едва след като при предходното извъкване са се изпълнили напълно. Нереентрантните функции не могат да бъдат рекурсивни. Те не могат да се използват, ако в програмата съществуват паралелно или квазипаралелно изпълняващи се нишки. Компилаторите оптимизират работата с променливи в стековата памет много по-добре, отколкото с променливи в статичната памет — такива променливи почти винаги са не в стека, а в регистрите на процесора. Инструкциите за работа със стекова памет при използваните процесори са почти толкова бързи, колкото и инструкциите за работа със статична памет.

Забележка

При някои от по-старите процесори (например 8-битовите) работата със стекова памет бе много по-неефективна. Поради това такъв тип памет се използваше рядко и дори операционната система бе нереентрантна.



[10] В действителност операционната система може да премества данните от такива променливи на различни места във физически наличната оперативна памет, както и да ги прехвърля временно на твърдия диск. Това обаче се извършва по прозрачен начин, т.е. „невидимо“ за програмиста, така че тук можем да игнорираме този тип преместване.

[11] При използване на С++ има един специален случай, при който това правило не е валидно. Той ще бъде описан по-нататък.