Как да разберете паметта на вашата програма

Когато кодирате на език като C или C ++, можете да взаимодействате с паметта си по по-ниско ниво. Понякога това създава много проблеми, които не сте получавали преди: сегментации . Тези грешки са доста досадни и могат да ви създадат много проблеми. Те често са индикатори, че използвате памет, която не трябва да използвате.

Един от най-често срещаните проблеми е достъпът до паметта, която вече е освободена. Това е памет, с която сте освободили freeили памет, която вашата програма е освободила автоматично, например от стека.

Разбирането на всичко това е наистина просто и определено ще ви накара да програмирате по-добре и по-умно.

Как се разделя паметта?

Паметта е разделена на множество сегменти. Две от най-важните за тази публикация са стекът и купчината . Стекът е подредено място за вмъкване, докато купчината е произволна - разпределяте памет, където можете.

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

Купчината често се използва за разпределяне на големи количества памет, за които се предполага, че съществуват, стига разработчикът да иска. Въпреки това задачата на разработчика е да контролира използването на паметта в купчината . Когато изграждате сложни програми, често трябва да отделяте големи парчета памет и там използвате купчината. Ние наричаме това динамична памет .

Поставяте нещата на купчината всеки път, когато използвате, за mallocда разпределите памет за нещо. Всяко друго повикване, което протича, int i;е стек памет. Знанието за това е наистина важно, за да можете лесно да намерите грешки във вашата програма и допълнително да подобрите търсенето на грешки по Segfault.

Разбиране на стека

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

И така, как наистина работи?

Стекът е структура на данни LIFO (Last-In-First-Out). Можете да го видите като кутия с перфектно монтирани книги - последната книга, която поставите, е първата, която извадите. Използвайки тази структура, програмата може лесно да управлява всички свои операции и обхвати, като използва две прости операции: натискане и изскачане .

Тези двама правят точно обратното един на друг. Push вмъква стойността в горната част на стека. Поп взема най-добрата стойност от него.

За да следите текущото място в паметта, има специален регистър на процесора, наречен Stack Pointer . Всеки път, когато трябва да запишете нещо - като променлива или адреса за връщане от функция - тя натиска и премества показалеца на стека нагоре. Всеки път, когато излезете от функция, тя изскача всичко от указателя на стека, докато запазеният връщащ адрес от функцията. Просто е!

За да проверим дали сте разбрали, нека използваме следния пример (опитайте да намерите грешката сама ☺️):

Ако го стартирате, програмата просто ще по подразбиране. Защо се случва това? Всичко изглежда на място! С изключение на ... стека.

Когато извикаме функцията createArray, стекът:

  • запазва адреса за връщане,
  • създава arrв паметта на стека и я връща (масивът е просто указател към място в паметта с неговата информация)
  • но тъй като не сме го използвали, mallocтой се записва в паметта на стека.

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

Разбиране на купчината

За разлика от стека, купчината е това, което използвате, когато искате нещо да съществува известно време, независимо от функциите и обхвата. За да използвам тази памет, езикът stdlib на C е наистина добър, тъй като носи две страхотни функции: mallocи free.

Malloc (разпределение на паметта) изисква системата за количеството памет, което е поискано, и връща указател на началния адрес. Free казва на системата, че паметта, която поискахме, вече не е необходима и може да се използва за други задачи. Изглежда наистина просто - стига да избягвате грешки.

Системата не може да отмени това, което са поискали разработчиците. Така че от нас, хората, зависи да го управляваме с двете функции по-горе. Това отваря вратата за една човешка грешка: Memory Leaks.

Изтичане на памет е памет, която е била поискана от потребителя и никога не е била освободена - когато програмата е приключила или са изгубени указатели към нейните местоположения. Това кара програмата да използва много повече памет от това, което е трябвало. За да избегнем това, всеки път, когато вече не се нуждаем от елемент, разпределен в купчина, го освобождаваме.

На горната снимка лошият начин никога не освобождава паметта, която използвахме. Това в крайна сметка губи 20 * 4 байта (int размер в 64-бита) = 80 байта. Това може да не изглежда толкова много, но представете си, че не правите това в гигантска програма. В крайна сметка можем да загубим гигабайта!

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

Бонус: Структури и купчината

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

Как решавам проблемите си с изтичане на памет

През повечето време, когато програмирам на C, използвам структури. Затова винаги имам две задължителни функции, които да използвам с моите конструкции : конструктор и деструктор .

Тези две функции са единствените, при които използвам mallocs и frees на структурата. Това го прави наистина лесно и лесно за разрешаване на изтичането на паметта ми.

(Ако искате да научите повече за улесняване на четенето на кода, проверете публикацията ми за абстракция).

Страхотен инструмент за управление на паметта - Valgrind

Трудно е да управлявате паметта си и да сте сигурни, че сте се справили с всичко правилно. Страхотен инструмент за проверка дали вашата програма се държи правилно е Valgrind. Този инструмент валидира вашата програма, като ви казва колко памет сте отделили, колко е освободен, ако сте се опитали да пишете в неправилна област на паметта ... Използването му е чудесен начин да проверите дали всичко е наред и трябва да го използвате, за да избегнете компромиси за сигурността.

Не забравяйте да ме следвате!

Освен да публикувам тук в Medium, аз съм и в Twitter.

Ако имате въпроси или предложения, не се колебайте да се свържете с мен.