Керування пам’яттю в програмах GTK

Переклад 19 хв. читання

Доповнений переклад PDF документу CSci493.70 "Introduction to Memory Management in GTK+" Стюарта Вайса - доцента Кафедри інформатики Гантерського коледжу Міського університету, Нью-Йорк.

Нотатки

  • Термін GTK+ було змінено на більш сучасний варіант назви - GTK
  • Переклад включає локальну адаптацію: корекцію семантики, мої доповнення та скорочення (де це можливо) також видалені не робочі посилання
  • Передбачається, що користувач володіє мінімальним досвідом програмування C++ та розуміє принцип роботи об'єктів, поінтерів та посилань

Вступ до керування пам'яттю в GTK

Метою цієї статті є допомога в розумінні того, як саме керується пам'ять у GTK, щоб ваші програми не мали витоків пам'яті і не завершували роботу аварійно.

В основному, існують лише два способи, якими ви можете зіпсувати програму стосовно того, як вона працює з пам'яттю:

  • програма не звільняє пам'ять, яка їй більше не потрібна, що призводить до витоку (наприклад, використання new без delete)
  • програма намагається звернутися до об'єкта, який більше не існує за адресою або цю адресу було змінено іншим компонентом програми

Витоки пам'яті

Типовим випадком для витоку пам'яті є створення нових об'єктів на кучі (heap) без подальшого їх видалення після використання:

MyClass* object = new MyClass;

// попрацювали...

// delete object; < забули

Можна подумати, що сьогодні не так вже й важливо запобігати витокам пам'яті. Адже на сучасних комп'ютерах її в досталь, а операційна система сама очистить виділені ресурси після завершення програми. Але якщо ви намагаєтеся бути професіоналом у своїй роботі, то не повинні використовувати більше пам'яті, ніж потрібно, оскільки це знижує продуктивність самої програми, а також впливає на продуктивність системи та енергоспоживання пристрою. Тим паче, якщо витік буде достатньо масштабним, то аварійно зупиниться не тільки програма, але й операційна система!

Недійсні посилання

Недійсні посилання неминуче призводять до збоїв, і їх однозначно потрібно уникати.

Звісно, вам не потрібно турбуватися, якщо ваша програма ніколи не виділяє пам'ять динамічно - у разі, якщо кожна змінна зберігається в стеку (під час виконання певної області видимості), або в статичній пам'яті (знаходиться в глобальній області видимості), тоді зв'язок адреси зі змінною є фіксованим.

Проте в GTK практично все, що створює програма, робиться через вказівники: віджети, списки та дерева, рядки тощо. І це не спроста, тому що такий підхід дозволяє не створювати зайві копії об'єктів та не розподіляти для них нову пам'ять - що значно прискорює швидкість та економічність програми.

Розглянемо простий приклад створення віджету кнопки:

GtkWidget *button;
button = gtk_button_new();
  • тут бачимо, що пам'ять для кнопки виділяється засобами GTK, а функція gtk_button_new() лише повертає вказівник на область пам'яті, де було збережено цей об'єкт. Таким чином, ваша програма отримує повний доступ до області пам'яті через цей вказівник, і тому існує ймовірність, що ви можете зіпсувати її роботу, якщо не розумієте, як саме керується пам'ять на рівні фреймворку GTK.

Облік посилань

Якщо ви зрозумієте, що таке облік посилань в C++, у вас не буде жодних проблем з керуванням пам'яттю. Якщо ж ні - то ідея в основному схожа з тим, яким чином UNIX керує дисковим сховищем для файлів для ефективного використання дискового простору. Якщо ви не знайомі з цим, то зараз саме час розібратися.

Приклад посилань I-node

В системах UNIX, файл представляється так званою і-нодою (i-node, або в перекладі - вузли) По суті, це структура мета-даних, що містить всю важливу інформацію про фізичний файл:

  • хто є його власником
  • хто має право робити з ним
  • який його розмір
  • де розташовані його блоки, а також багато іншого.

І-нода також містить лічильник посилань для кожного екземпляру імені файлу. Таким чином, одні й ті само фізичні файли можуть мати багато імен, в одному або різних каталогах і навіть файлових системах, при цьому дані самого файлу зберігаються в єдиному екземплярі.

Уявімо, хтось інший створює ім'я для файлу в іншому місці, лічильник посилань збільшиться. Кожного разу, коли ім'я видаляється, лічильник посилань зменшується. Якщо лічильник посилань досягає нуля, i-нода видаляється, а також звільняється пам'ять файлу.

Користувачам Windows буде простіше зрозуміти суть посилань на прикладі ярлика програми - який може мати багато екземплярів у різних розташуваннях але при цьому посилатись на один і той само об'єкт - з робочого столу, меню чи будь якої іншої теки. Проте саме приклад і-ноди в UNIX описує, що являють собою посилання в C++ та їх лічильники в GTK.

Дізнатись більше про і-ноди в UNIX можна тут і тут.

Організація посилань в GTK

В об'єктах GTK також використовується облік посилань. У той час як в UNIX лічильник посилань - це кількість імен, які має файл, то в GTK це кількість власників об'єкта.

Таким чином, кожен новий об'єкт GTK створюються з лічильником посилань і спочатку дорівнює одному. Кожного разу, коли створюється нове посилання на об'єкт, його лічильник посилань збільшується. Коли посилання видаляється, лічильник зменшується. Якщо лічильник досягає нуля, пам'ять об'єкта звільняється. Акт звільнення пам'яті, пов'язаної з об'єктом, називається фіналізацією об'єкта.

Концепція була б досить простою для розуміння, якби не той факт, що існує два різних типи об'єктів:

  • ті, що походять від GInitiallyUnowned
  • ті, що не походять

Нагадаємо, що верхня частина ієрархії об'єктів виглядає так:

GObject
    +----GInitiallyUnowned
        +----GtkObject
            +----GtkWidget

На прикладі вище можемо бачити, що GtkWidget походить від GtkObject, який, у свою чергу, походить від GInitiallyUnowned. Ц означає, що кожен віджет непрямо походить від GInitiallyUnowned.

Що ж, можливо, ви запитаєте, що ж таке GObject, який не є GInitiallyUnowned? Цей список досить довгий і включає загальновживані об'єкти, зокрема такі:

  • GtkAction
  • GtkListStore
  • GtkPrinter
  • GtkStyle
  • GtkTextBuffer
  • GtkTextMark
  • GtkTextTag
  • GtkTreeSelection
  • GtkTreeStore
  • GtkUIManager

Саме об'єкти, які походять від GObject, стануть нашою відправною точкою, оскільки їх легше зрозуміти.

Прямі нащадки GObject

Об'єкти, що напряму походять від GObject, створюються з початковим лічильником посилань, рівним одному. Тож давайте створимо декілька таких об'єктів, щоб продемонструвати їх принцип роботи.

GtkListStore використовується в GTK доволі часто, наприклад з віджетами дерева у форматі рядків і стовпців, подібно до того, як це робить файловий браузер.

Створити його можна наступним чином:

GtkListStore* store = gtk_list_store_new();

Функція створює об'єкт та повертає нам вказівник в змінну store з лічильником посилань, рівним одному.

Документація зазначає:

The caller of the function takes ownership of the data, and is responsible for freeing it.

Це значить, що створений таким чином об'єкт - володітиме посиланням, тому лічильник посилань для store можна збільшити, викликавши g_object_ref():

g_object_ref(G_OBJECT( store ) );

відповідно, щоб зменшити лічильник об'єкта store, потрібно викликати:

g_object_unref( G_OBJECT( store ) );

Уявімо, що у нас є певний об'єкт дерева treeview, який вже було створено раніше.

Спробуємо додати до нього наш список store:

gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(store));

Після того, як функція gtk_tree_view_set_model() повернулася, віджет дерева treeview відтепер тепер буде володіти посиланням на store.

Такі методи як gtk_tree_view_set_model() - набувають право власності на об'єкт і самостійно збільшують лічильник посилань об'єкта, використовуючи g_object_ref() у своїх реалізаціях. Якщо б вони цього не робили, то програма коли-небудь викликала g_object_unref(), лічильник посилань знизився б до нуля і був би фіналізований. У цей момент з'явилося б посилання на недійсну пам'ять, і при наступній спробі доступу до даних програмою - стався б збій. Отже, після того, як список зберігань був доданий до віджета дерева, його лічильник посилань становитиме два.

Спосіб, яким GtkTreeView робить GtkListStore тим, що він відображає, подібний до того, як віджет GtkTextView робить текстовий буфер тим, що він відображає, використовуючи gtk_text_view_set_buffer():

gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), GTK_TEXT_BUFFER(buffer));

Оскільки текстовий віджет набуває право власності на buffer, gtk_text_view_set_buffer() збільшує лічильник посилань buffer. Якщо buffer був створений якою-небудь іншою частиною коду, яка набула право власності під час створення, лічильник посилань buffer становитиме два, що вказує на те, що buffer має двох власників (текстові буфери можуть бути спільними для кількох віджетів текстового перегляду)

Повернемося до обговорення нашого списку зберігань store. Уявімо, що нашій програмі більше не потрібно отримувати доступ до списку зберігань, як тільки він буде доданий до віджета дерева. Адже увесь доступ до списку зберігань тепер відбувається через віджет дерева. Тому, як тільки вона викликала gtk_tree_view_set_model(), вона повинна відмовитися від свого посилання та зменшити лічильник посилань за допомогою g_object_unref() таким чином:

gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(store));
g_object_unref(G_OBJECT(store));

Це забезпечує зниження лічильника посилань до одного. Тому, коли віджет дерева treeview буде знищено і він звільнить усі свої дочірні об'єкти, лічильник посилань store стане нульовим, і він також буде фіналізований.

Підсумовуючи, коли ваша програма створює об'єкт, що безпосередньо походить від GObject, як тільки вона приєднала його до певного об'єкта або віджета GTK, який припускає право власності, ви повинні викликати g_object_unref() на об'єкті, який вам більше не потрібен.

Об'єкти, що походять від GInitiallyUnowned

Ситуація зовсім інша для об'єктів, що походять від GInitiallyUnowned. Усі такі об'єкти створюються з плаваючим лічильником посилань, рівним одному. Плаваюче посилання - це посилання, яке не належить нікому. Саме тому клас називається GInitiallyUnowned, оскільки спочатку об'єкти цього класу не мають власника. Плаваюче посилання можна розглядати як особливий вид посилання, яке не асоційоване з жодним власником. Технічно це прапорець у структурі GObject який вказує, чи є початкове посилання плаваючим чи ні; коли він встановлений, об'єкт має плаваюче посилання, а коли він очищений, посилання є неплаваючим.

Необхідність плаваючих посилань має дві причини. Одна з причин полягає в тому, що вони є способом зберегти об'єкти після їх створення, але до того, як вони будуть приєднані до батьківських контейнерів. Інша причина полягає у тому, як функції можуть бути викликані в C.

Розглянемо наступний код:

container = create_container();
container_add_child (container, create_child());

Припустимо, що container - це якийсь тип контейнерного віджета, а container_add_child() додає дочірній віджет до даного контейнера а також додає посилання на даний дочірній об'єкт, який у цьому випадку є об'єктом, створеним викликом create_child().

Припустимо, що плаваючих посилань не існує і що create_child() створює об'єкт з лічильником посилань, рівним одному. Оскільки контейнер набуває посилання на об'єкт і збільшує лічильник посилань, після виклику, дочірній об'єкт матиме лічильник посилань рівний двом.

Однак, оскільки значення, що повертається create_child(), не присвоюється жодному об'єкту, немає об'єкта, на якому можна викликати g_object_unref(), щоб зменшити лічильник до одного. Коли контейнер знищується, він може "звільнити" дочірній об'єкт, але лічильник дочірнього об'єкта знизиться до одного, а не до нуля, і це призведе до витоку пам'яті, оскільки пам'ять дочірнього об'єкта не може бути звільнена, поки лічильник посилань не досягне нуля.

Щоб запобігти витоку пам'яті за відсутності плаваючих посилань, можна використати наступний код:

Child *child;
container = create_container(); // container ref-count = 1
child = create_child(); // child ref-count = 1
container_add_child (container, child); // child ref-count = 2
g_object_unref (child); // child ref-count = 1

Різниця тут полягає в тому, що результат create_child() зберігається в child, тому до нього можна застосувати g_object_unref(). Однак, оскільки можливо написати код у C і в першому варіанті, бібліотека GObject була спроектована так, щоб такий код працював правильно.

Ідея плаваючих посилань дозволяє наведеному коду бути вільним від витоків пам'яті, але лише якщо контейнер має можливість набути право власності та перетворити плаваюче посилання на стандартне посилання, яке називається звичайними посиланням.

Відмінність наступна:

  • функція, яка створює віджет, така як create_child() вище - створює його з плаваючим лічильником посилань рівним одному. Тобто таким, що спочатку не має власника, а не зі стандартним лічильником посилань, рівним одному.
  • функція, яка додає дочірній віджет до контейнера, така як container_add_child() вище - не викликає g_object_ref() на дочірньому об'єкті, оскільки це не очистить прапорець плаваючого посилання. Натомість вона викликає g_object_ref_sink()
g_object_ref_sink(object);

Функція g_object_ref_sink() існує для того, щоб, коли віджет додається до батьківського контейнера, контейнер міг зробити дві речі:

  • видалити плаваюче посилання
  • набути власне стандартне посилання на віджет

Вона працює наступним чином: якщо object мав плаваюче посилання, то викликаючи її:

  • програма володіє посиланням на object
  • лічильник посилань залишається не змінним
  • прапорець плаваючого посилання очищується

Якщо object не мав плаваючого посилання, то g_object_ref_sink() має той само ефект, як і g_object_ref(), а саме - збільшує лічильник посилань.

Функціонально, операцію "Sink" можна розглянути як:

if ( was_floating(object) )
    clear( object->floating_flag );
else
    g_object_ref(object);

Тепер повернемось до прикладу вище:

container = create_container();
container_add_child (container, create_child());

create_child() створює віджет з плаваючим лічильником посилань, рівним одному, а функція container_add_child() викликає g_object_ref_sink() на анонімному дочірньому віджеті (як об'єкті, звичайно), набуваючи право власності на нього, що надає йому стандартний лічильник посилань, рівний одному. Коли контейнер знищується, він звільнить дочірній об'єкт, зменшуючи його лічильник до нуля, що призведе до фіналізації дочірнього об'єкта без необхідності для програми викликати g_object_unref() на дочірньому об'єкті після container_add_child().

Усі віджети, за винятком вікон верхнього рівня, починають своє життя з плаваючим посиланням. Вікна верхнього рівня відрізняються, оскільки вони ніколи не поміщаються в контейнери - вони є коренями дерев контейнерів. Коли вікно верхнього рівня створюється, бібліотека GTK відразу ж "зливає" його плаваюче посилання і набуває право власності на нього, тому, коли воно передається вашій програмі, його стандартний лічильник посилань становитиме один.

Якщо програма створює віджет, який не є вікном верхнього рівня, то в якийсь момент його, ймовірно, буде упаковано в контейнер. Усі функції, які упаковують віджети в контейнери, автоматично "зливає" плаваюче посилання віджета і надають контейнеру право власності на дочірній об'єкт. Таким чином, коли батьківський віджет зрештою отримує сигнал знищення і звільняє свої дочірні об'єкти, їх стандартні лічильники посилань знизяться до нуля, і вони будуть фіналізовані (якщо в програмі, з тієї чи іншої причини, код не викликає g_object_ref() на будь-якому з дочірніх об'єктів без відповідного g_object_unref(), що призведе до витоку пам'яті!).

Щоб було зрозуміло, коли ваша програма створює віджет типу foo, використовуючи конструктор, такий як gtk_foo_new(), і додає цей віджет до контейнера, їй ніколи не потрібно робити нічого іншого для керування пам'яттю віджета; GTK подбає про звільнення пам'яті, коли віджет буде знищено.

Спробуймо розкрити суть на прикладі:

GtkWidget *gadget = gtk_gadget_new ();
gtk_widget_destroy (gadget);

Можна очікувати, що конструкція просто створить gadget, виділяючи пам'ять для нього, а потім відразу ж її звільнить. На перший погляд це здається логічним. Але насправді це призведе до витоку пам'яті!

Це тому, що коли gadget створюється, він має лише плаваюче посилання, а виклик gtk_widget_destroy() еквівалентний конструкції:

gtk_object_destroy (GTK_OBJECT(gadget));

Документація для gtk_object_destroy стверджує, що:

Пам'ять для самого об'єкта не буде видалена, поки його лічильник посилань фактично не знизиться до нуля; gtk_object_destroy() лише просить власників посилань звільнити свої посилання, вона не звільняє об'єкт.

Іншими словами, gtk_widget_destroy() не звільняє об'єкт; вона лише просить власників посилань відпустити свої посилання. Оскільки gadget не був упакований у жоден контейнер, а отже g_object_ref_sink() - не був викликаний на ньому, він не належить жодному власнику посилань, і тому gtk_widget_destroy() не звільнить пам'ять, що утримується gadget. В результаті виникає витік пам'яті.

Звичайно, немає жодної причини писати код таким чином. Але в разі якоїсь причини, коли ви хочете створити віджет, а потім знищити його і звільнити пам'ять, яку він утримує (не додаючи його до контейнера) вам потрібно буде звільнити його самостійно:

GtkWidget *gadget = gtk_gadget_new();
g_object_ref_sink(G_OBJECT(gadget)); // перетворити плаваюче посилання на стандартне
gtk_widget_destroy(gadget); // знищити зовнішні посилання
g_object_unref(G_OBJECT(gadget)); // зменшити лічильник посилань до 0

Знову таки, немає особливої причини створювати віджет, який ви не маєте наміру додавати до контейнера, за винятком вікна верхнього рівня. Метою попереднього прикладу лише пояснення, як працює облік посилань стосовно об'єктів, таких як віджети, зокрема які походять від GInitiallyUnowned.

Типи рядків GLib

Існують різні типи рядків, які можна використовувати в програмі GTK на мові C:

char *string1; // стандартний рядок C
gchar *string2; // ідентичний стандартному рядку C, оскільки gchar є типом даних для char
GString string3; // розширення рядків C, яке може автоматично зростати

При використанні стандартних функцій рядків, таких як g_strdup(), g_strnfill(), або g_strdup_printf(), загальне правило полягає в тому, якщо документація говорить, що функція повертає новостворений рядок, то повернутий рядок слід звільнити за допомогою g_free().

Зазвичай документація чітко вказує, що повернутий рядок повинен бути звільнений за допомогою g_free(). Якщо жодне з цих тверджень не міститься, то не викликайте g_free() на рядок, якщо не хочете, щоб програма зазнала збою.

Об'єкти GString

Об'єкт GString по суті є структурою, пам'яттю якої керує бібліотека GLib:

typedef struct {
    gchar *str;
    gsize len;
    gsize allocated_len;
} GString;

Внутрішній вказівник str є адресою текстового буфера і може переміщатися в міру зростання та зменшення рядка.

Об'єкт GString створюється за допомогою однієї з функцій сімейства g_string_new(). Після роботи з об'єктом, для вивільнення пам'яті використовується функція g_string_free(). Якщо ви хочете, щоб сама структура та вміст її буфера були звільнені, то виклик буде таким:

gchar* buf = g_string_free(string, TRUE);
  • другий аргумент вказує, чи буде звільнено також дані рядка, або лише обгорткова структура, тобто якщо передано TRUE, значення, що повертається, буде NULL але якщо другий аргумент - FALSE, то буфер не буде звільнений, і значення, що повертається - буде вказівником на буфер, який потрібно звільнити, коли код закінчить роботу з ним, використовуючи g_free()

Структури GdkPixbuf

Структури GdkPixbuf безпосередньо походять від GObject і відповідно - реалізують облік посилань. Вони створюються з лічильником посилань, рівним одному. Програма може ділитися одним піксельним буфером pixbuf з багатьма частинами учасниками. Коли певна частина програми має утримувати вказівник на піксельний буфер, вона повинна додати посилання на нього, викликавши g_object_ref(). Коли вона закінчить з піксельним буфером, їй слід викликати g_object_unref(), щоб зменшити лічильник посилань. Піксельний буфер буде знищено, коли його лічильник посилань знизиться до нуля. Лічильник посилань загалом повинен бути рівним кількості різних вказівників на об'єкт.

  • діалоги не додаються до контейнерів, але знищуються за допомогою gtk_widget_destroy(), і це відбувається тому, що діалоги є вікнами верхнього рівня, а GTK має посилання на них. Сигнал знищення викликає у GTK виклик unref на діалозі, звільняючи його пам'ять.

Правила, описані вище для рядкових типів C, загалом застосовуються і до pixbuf. Деякі функції, які повертають вказівник на GdkPixbuf, акцентують у своїй документації той факт, що вони повертають новий об'єкт pixbuf з лічильником посилань (що дорівнює одному). Якщо ви використовуєте таку функцію, то ви повинні викликати g_object_unref() після того, як закінчите використовувати новозалучений pixbuf. Якщо pixbuf використовувався як джерело для створення нового pixbuf (у випадку з gdk_pixbuf_add_alpha(), який створює модифіковану версію pixbuf, і оригінал більше не потрібен) то потрібно викликати g_object_unref(), щоб відмовитися від володіння і зменшити лічильник посилань.

Останнє, про що вам потрібно знати щодо pixbuf, це те, що якщо ви використовуєте gdk_pixbuf_new_from_data() для створення pixbuf з даних, які вже зберігаються в пам'яті (наприклад, масив значень пікселів) то вам також може знадобитися створити функцію, яка "знає, як" звільнити пам'ять, що належить цим даним, коли лічильник посилань на pixbuf зменшується до нуля.

Списки і деревоподібні сховища

Інтерфейс GtkTreeModel визначає загальний інтерфейс дерева для використання віджетом GtkTreeView. Це абстрактний інтерфейс, який розроблений для використання з будь-якою структурою даних, яка йому відповідає.

GtkTreeStore та GtkListStore - це вже реалізовані моделі дерева GTK. Вони надають структуру даних, а також усі відповідні інтерфейси дерева. Заповнення їх даними вимагає використання або методу gtk_list_store_set(), або методу gtk_tree_store_set() відповідно. Існують варіації цих двох функцій, але зауваження нижче є вірними незалежно від того, яка варіація використовується.

Обидві функції gtk_list_store_set() і gtk_tree_store_set() приймають змінну кількість аргументів, які по суті є впорядкованими парами, як у прикладі нижче:

void gtk_tree_store_set (GtkTreeStore *store,
                         GtkTreeIter *iter,
                         ..., -1);

Відсутніми аргументами є пари (column_id, value), де column_id - це ціле число, а value може бути будь-якого типу: рядком, цилим числом, pixbuf або вказівником на довільні структури. Що потрібно знати, так це те, чи передані цій функції дані копіюються у рядок сховища або вони доступні за посиланням.

В наступному прикладі, питання полягає в тому, чи фактичні рядкові дані (які зберігаються в змінній name) копіюються в рядок, на який вказує ітератор, чи в рядок копіюється саме вказівник на name:

gchar name[] = "Groucho";
gtk_tree_store_set (store, &iter, NAME_ID, name, -1);

Коротка відповідь: вам не потрібно турбуватися про виділення та звільнення пам'яті для даних, які потрібно зберігати, оскільки підсистема GLib/GObject GType і GValue автоматично займається більшістю керування пам'яттю. Наприклад, якщо ви зберігаєте рядок у стовпці рядка, модель зробить копію рядка і зберігатиме цю копію. Якщо ви пізніше зміните стовпець на новий рядок, модель автоматично звільнить старий рядок і знову зробить копію нового рядка та зберігатиме цю копію.

Довга відповідь полягає в тому, що коли дані додаються до списку або дерева за допомогою будь-якої з функцій gtk_*_store_set() те, як вони обробляються GTK, залежить від того, до якої з трьох категорій даних вони належать:

  1. Якщо дані є GObject (тобто об'єктом, що безпосередньо походить від GObject), то сховище бере на себе право власності на нього, викликаючи g_value_dup_object() для об'єкта та відмовляючись від права власності на раніше утримуваний об'єкт, якщо він замінює старе значення.

  2. Якщо дані є простим скалярним типом даних, таким як числовий, логічний або перерахунковий тип, або вказівником, то сховище робить копію даних. Зверніть увагу, що копіюється вказівник, а не дані, на які він вказує.

  3. Якщо дані є рядком або упакованою структурою, то сховище дублює рядок або упаковану структуру та зберігає вказівник на неї. Якщо дані замінюють існуючі дані, то в цьому випадку рядок або упакована структура спочатку звільняються за допомогою g_free() або g_boxed_free() відповідно (GBoxed - це механізм обгортки для довільних структур C, вони обробляються як непрозорі шматки пам'яті)

Це може залишити вас з питаннями як GTK обробляє pixbuf. Як зазначалося вище, pixbuf безпосередньо походить від GObject і підпадає під першу категорію. Коли ви отримуєте дані зі сховища за допомогою gtk_tree_model_get() або будь-якої з його варіацій, вам потрібно бути обізнаним про те, як GTK обробляє різні типи даних, щоб знати, чи потрібно звільняти пам'ять, коли ви закінчите з ними:

  1. Якщо отримувані дані є GObject, функція збільшує його лічильник посилань, оскільки вона надає ще одне посилання на нього для нас.
  2. Якщо дані є простим скалярним типом даних, таким як числовий, логічний або перерахунковий тип, або вказівником, функція робить копію і повертає цю копію.
  3. Якщо дані є рядком або упакованим типом, функція копіює їх і повертає вказівник на дані.

Це означає, що потрібно викликати g_object_unref() для об'єктів, отриманих зі сховища, коли роботу з ними завершено, а також - звільнити дані, які ми отримали зі сховища, якщо це рядкові або упаковані типи даних (використовуючи g_free() або g_boxed_free() відповідно).

Щодо pixbuf, то такі дані належать до першої категорії, тому спочатку необхідно відмовитися від права власності, отримане зі сховища за допомогою gtk_tree_model_get(), за допомогою unref. Усі інші дані не потребують додаткового керування пам'яттю.

Висновки

Коли ви налагоджуєте свою програму, хорошою ідеєю буде увімкнення системного монітора та спостереження за використанням віртуальної та фізичної пам'яті програми. Якщо ви бачите, що використання пам'яті постійно зростає під час роботи програми, це означає, що у вас десь є витік пам'яті. Моя порада - повторювати дії, такі як багаторазове натискання на один і той же пункт меню, щоб виявити, що є винуватцем.

Якщо ви звільнили пам'ять, яку не повинні були звільняти, це буде очевидно, оскільки програма аварійно завершить роботу. Вам слід переглянути звіт про помилку, виданий вашим менеджером вікон (наприклад, Gnome), і подивитися на трасу стека. Починаючи з верхньої частини стека, спускайтеся, поки не знайдете першу функцію, код якої належить вам, а не бібліотеці GTK. Саме там знаходиться код, який спричинив проблему.

Існують більш формальні методи виявлення витоків пам'яті, але попередній підхід є відносно простим у виконанні і не вимагає вивчення нового інструменту. Якщо ви хочете навчитися користуватися спеціалізованим інструментарієм, спробуйте Valgrind - безкоштовне програмне забезпечення з відкритим кодом для аналізу та керування пам'яттю.

Джерело: Introduction to Memory Management in GTK+
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
p.s. 626
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід