Ког­да чита­ешь дис­куссии меж­ду сто­рон­никами и про­тив­никами авто­мати­чес­кого управле­ния памятью, может сло­жить­ся впе­чат­ление, буд­то это какая‑то еди­ная тех­нология, оди­нако­во реали­зован­ная во всех язы­ках прог­рамми­рова­ния. В этой статье мы погово­рим о сбор­ке мусора — наибо­лее рас­простра­нен­ном механиз­ме управле­ния памятью.

Спо­ры на эту тему вооб­ще неред­ко зву­чат как «чис­тый С про­тив осталь­ных язы­ков». В дей­стви­тель­нос­ти авто­мати­чес­кое управле­ние памятью в С впол­не воз­можно и при­меня­ется на прак­тике, да и «осталь­ные язы­ки» и раз­ные их реали­зации силь­но отли­чают­ся друг от дру­га.

 

Ошибки управления памятью

Для начала вспом­ним, от чего нас спа­сает авто­мати­чес­кое управле­ние памятью.

Пер­вая и самая извес­тная, но при этом не самая опас­ная — утеч­ка памяти (memory leak). Утеч­ка про­исхо­дит, если зап­росить у ядра ОС память и забыть ее вер­нуть. В тер­минах язы­ка С — выз­вать malloc() и забыть free(). Прог­рамма с этой проб­лемой будет занимать все боль­ше и боль­ше памяти, пока ее не оста­новит поль­зователь или сама ОС. Поведе­ние прог­раммы при этом оста­ется кор­рек­тным, и проб­лем с безопас­ностью утеч­ки не вызыва­ют.

Вто­рая проб­лема — висячие ука­зате­ли (dangling pointers). Суть проб­лемы в том, что в прог­рамме оста­ется ука­затель на учас­ток памяти, который уже был осво­бож­ден. Для пов­торно­го обра­щения к такой памяти есть отдель­ный тер­мин — use after free. Такие ошиб­ки гораз­до опас­нее, и пос­ледс­твия могут быть самыми раз­ными: от слож­ных в отладке глю­ков до воз­можнос­ти выпол­нить про­изволь­ный код — база CVE не даст сов­рать.

Бо­лее ред­кий вари­ант проб­лемы с висячим ука­зате­лем — пов­торное осво­бож­дение (double free), которое унич­тожа­ет полез­ные дан­ные.

Та­ким обра­зом, от решения для авто­мати­чес­кого управле­ния тре­буют­ся два свой­ства: никог­да не уда­лять из памяти объ­екты, на которые есть живые ука­зате­ли, и по воз­можнос­ти не оставлять в памяти объ­екты, на которые живых ука­зате­лей нет.

 

Что делает сборщик мусора?

Уп­рощен­но мож­но ска­зать, что при запус­ке у прог­раммы есть неп­рерыв­ный диапа­зон адре­сов, куда она может помес­тить свои дан­ные. Прог­рамма с авто­мати­чес­ким управле­нием памятью сра­зу при запус­ке зап­рашива­ет у ОС область памяти под «кучу» (heap). Началь­ный раз­мер кучи час­то (но не всег­да) мож­но нас­тро­ить во вре­мя ком­пиляции или выпол­нения. При выпол­нении прог­раммы раз­мер кучи может рас­ти.

Пос­ле это­го сбор­щик мусора пери­оди­чес­ки сле­дит за тем, какие учас­тки памяти еще содер­жат нуж­ные дан­ные, а какие мож­но осво­бодить и запол­нить новыми дан­ными. Как имен­но он это дела­ет — зависит от реали­зации, но об этом даль­ше. Для начала раз­веем более прос­тые мифы.

 

Сборщик мусора — часть языка?

Час­то мож­но услы­шать утвер­жде­ния вро­де «Ruby — язык со сбор­кой мусора» или «С — язык с руч­ным управле­нием памятью». Пер­вое утвер­жде­ние вер­но в том смыс­ле, что ни одна реали­зация Ruby не пре­дос­тавля­ет воз­можность управлять памятью вруч­ную.

Со вто­рым утвер­жде­нием слож­нее. Сбор­ка мусора не вхо­дит в спе­цифи­кацию язы­ка С. Тем не менее спе­цифи­кация ее и не зап­реща­ет. Спе­цифи­кация язы­ка ада так­же не навязы­вает авто­рам ком­пилято­ров какую‑то кон­крет­ную модель управле­ния памятью, но некото­рые ком­пилято­ры при этом пре­дос­тавля­ют опци­аль­ный сбор­щик мусора.

Та­кие ком­пилято­ры С мне неиз­вес­тны, но на прак­тике авто­мати­чес­ки управлять памятью в прог­раммах на С впол­не воз­можно с помощью сто­рон­них биб­лиотек.

Для при­мера мы возь­мем Boehm GC. Это весь­ма зре­лый и фун­кци­ональ­ный про­дукт, который исполь­зовали или поныне исполь­зуют мно­жес­тво про­ектов: как при­ложе­ний (нап­ример, век­торный гра­фичес­кий редак­тор Inkscape), так и реали­заций язы­ков прог­рамми­рова­ния.

 

Используем Boehm GC

Мно­гие дис­три­бути­вы Linux пре­дос­тавля­ют пакет с Boehm GC в репози­тори­ях, чаще все­го под име­нем libgc. В Fedora его мож­но пос­тавить коман­дой sudo dnf install libgc-devel, в Debian — sudo apt-get install libgc-dev.

Для демонс­тра­ции мы напишем прог­рамму, которая неп­рерыв­но зап­рашива­ет память под мас­сив из тысячи целых чисел, но никог­да ее не осво­бож­дает. Если бы мы исполь­зовали для выделе­ния памяти клас­сичес­кий malloc(), это была бы хрес­томатий­ная утеч­ка памяти. Но мы обра­тим­ся не нап­рямую к ОС, а к менед­жеру памяти Boehm GC с помощью фун­кции GC_MALLOC() и пос­мотрим, что будет.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

3 комментария

  1. Аватар

    NoctuaArcticum

    23.10.2020 в 09:16

    Здравствуйте! Поясните, пожалуйста, что такое «отзывчивость программы» в рамках однопоточного консольного приложения написанного на Си.

    • Аватар

      Laglag

      26.10.2020 в 10:51

      Попробуйте написать программу. Выделить память и обращаться к ней по арессу( имени массива, имени функции). Есть разные типы данных и разные структуры доступа к примеру одна переменная или функция может работать не одинаково если написать не грамотно в кучу, и будет зацикоено обращение и выйти бывает не легко из цикла, не об этом. Приватная и публичная функция. Тут о сборке мусора. На сторону отдавать это страшно. Из за безопасности. Я очень плохо пишу. Идёт война постоянная с написанием. Мне простительно.

  2. Аватар

    Garta

    26.10.2020 в 10:57

    Интересная ознакомительная статья

Оставить мнение