C Управление памятью

Я всегда слышал, что в C вы должны смотреть, как вы управляете памятью. И я все еще начинаю изучать C, но до сих пор мне не приходилось делать какие-либо операции с управлением памятью. Я всегда думал, что нужно освобождать переменные и делать всевозможные уродливые вещи. Но, похоже, это не так.

Может ли кто-нибудь показать мне (с примерами кода) пример того, когда вам нужно будет «управлять памятью»?

Есть два места, где переменные могут быть помещены в память. Когда вы создаете переменную следующим образом:

int a; char c; char d[16]; 

Переменные создаются в « стеке ». Переменные стека автоматически освобождаются, когда они выходят за frameworks (то есть, когда код больше не может их до них донести). Вы могли бы услышать, что они называются «автоматическими» переменными, но это вышло из моды.

Многие начинающие примеры будут использовать только переменные стека.

Стол хорош, потому что он автоматичен, но он также имеет два недостатка: (1) Компилятор должен заранее знать, насколько велики переменные, и (б) пространство стека несколько ограничено. Например: в Windows, при настройках по умолчанию для компоновщика Microsoft, стек установлен в 1 МБ, и не все это доступно для ваших переменных.

Если во время компиляции вы не знаете, насколько велик ваш массив, или вам нужен большой массив или структура, вам нужен «план B».

План B называется « кучей ». Обычно вы можете создавать переменные размером с операционную систему, но вы должны сделать это самостоятельно. Раньше сообщения показывали вам один способ, которым вы можете это сделать, хотя есть и другие способы:

 int size; // ... // Set size to some value, based on information available at run-time. Then: // ... char *p = (char *)malloc(size); 

(Обратите внимание, что переменные в куче не обрабатываются напрямую, а через указатели)

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

 free(p); 

Что делает этот второй вариант «неприятным бизнесом» в том, что не всегда легко узнать, когда переменная больше не нужна. Забыв освободить переменную, когда она вам не понадобится, она заставит вашу программу потреблять больше памяти, в которой она нуждается. Эта ситуация называется «утечкой». «Просочившаяся» память не может использоваться ни для чего, пока ваша программа не закончится, и ОС не восстановит все свои ресурсы. Даже более неприятные проблемы возможны, если вы освободите переменную кучи по ошибке, прежде чем вы на самом деле с ней справитесь.

В C и C ++ вы несете ответственность за очистку переменных кучи, как показано выше. Однако существуют языки и среды, такие как языки Java и .NET, такие как C #, которые используют другой подход, когда куча сама очищается. Этот второй метод, называемый «garbage collection», намного проще для разработчика, но вы платите штраф за накладные расходы и производительность. Это баланс.

(Я прояснил многие подробности, чтобы дать более простой, но, надеюсь, более уравновешенный ответ)

Вот пример. Предположим, что у вас есть функция strdup (), которая дублирует строку:

 char *strdup(char *src) { char * dest; dest = malloc(strlen(src) + 1); if (dest == NULL) abort(); strcpy(dest, src); return dest; } 

И вы называете это так:

 main() { char *s; s = strdup("hello"); printf("%s\n", s); s = strdup("world"); printf("%s\n", s); } 

Вы можете видеть, что программа работает, но вы выделили память (через malloc), не освобождая ее. Вы потеряли указатель на первый блок памяти, когда вы вызывали strdup во второй раз.

Это не имеет большого значения для этого небольшого объема памяти, но рассмотрим случай:

 for (i = 0; i < 1000000000; ++i) /* billion times */ s = strdup("hello world"); /* 11 bytes */ 

Теперь вы использовали 11 гигабайт памяти (возможно, больше, в зависимости от вашего диспетчера памяти), и если вы не разбили свой процесс, вероятно, работает довольно медленно.

Чтобы исправить это, вам нужно вызвать free () для всего, что получено с помощью malloc (), после того, как вы закончите использовать его:

 s = strdup("hello"); free(s); /* now not leaking memory! */ s = strdup("world"); ... 

Надеюсь, этот пример поможет!

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

  char *string = malloc(stringlength); // stringlength is the number of bytes to allocate // Do something with the string... free(string); // Free the allocated memory 

Я думаю, что наиболее сжатый способ ответить на вопрос, чтобы рассмотреть роль указателя в C. Указатель – легкий, но мощный механизм, который дает вам огромную свободу за счет огромной способности стрелять в ногу.

В C ответственность за то, что ваши указатели указывают на вашу память, принадлежит вам и вашим. Для этого требуется организованный и дисциплинированный подход, если вы не откажетесь от указателей, что затрудняет создание эффективной C.

Опубликованные ответы на сегодняшний день сосредоточены на распределении переменных автоматического (стека) и кучи. Использование распределения стека делает для автоматически управляемой и удобной памяти, но в некоторых случаях (большие буферы, рекурсивные алгоритмы) это может привести к ужасающей проблеме переполнения стека. Знание того, сколько памяти вы можете выделить в стеке, очень зависит от системы. В некоторых встроенных сценариях несколько десятков байтов могут быть вашим лимитом, в некоторых сценариях на рабочем столе вы можете безопасно использовать мегабайты.

Распределение кучи менее присуще языку. Это в основном набор вызовов библиотеки, который предоставляет вам право собственности на блок памяти заданного размера, пока вы не будете готовы вернуться («бесплатно»). Это звучит просто, но связано с невыразимой печалью программиста. Проблемы просты (освобождение одной и той же памяти в два раза или вообще не происходит [утечки памяти], а не выделение достаточной памяти [переполнение буфера] и т. Д.), Но их трудно избежать и отлаживать. Высокий дисциплинированный подход является абсолютно обязательным в практике, но, конечно, язык на самом деле не предусматривает его.

Я хотел бы упомянуть еще один тип распределения памяти, который был проигнорирован другими сообщениями. Можно статически распределять переменные, объявляя их вне любой функции. Я думаю, что в целом этот тип распределения получает плохой рэп, потому что он используется глобальными переменными. Однако нет ничего, что говорит о том, что единственный способ использовать память, выделенную таким образом, – это недисциплинированная глобальная переменная в беспорядке кода спагетти. Статический метод распределения можно использовать просто, чтобы избежать некоторых ошибок в куче и методах автоматического распределения. Некоторые программисты С удивлены, узнав, что большие и сложные C встроенные и игровые программы были построены без использования распределения кучи вообще.

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

Здесь есть несколько отличных ответов о том, как распределить и освободить память, и, на мой взгляд, более сложной стороной использования C является то, что единственная память, которую вы используете, – это память, которую вы выделили, – если это не сделано правильно, что вы заканчиваете с кузеном этого сайта – переполнение буфера – и вы можете перезаписывать память, которая используется другим приложением, с очень непредсказуемыми результатами.

Пример:

 int main() { char* myString = (char*)malloc(5*sizeof(char)); myString = "abcd"; } 

На данный момент вы выделили 5 байтов для myString и заполнили его «abcd \ 0» (строки заканчиваются нулем – \ 0). Если выделение строки было

 myString = "abcde"; 

Вы назначили бы «abcde» в 5 байтах, которые вы выделили для своей программы, а конечный нулевой символ будет помещен в конце этого – часть памяти, которая не была выделена для вашего использования, и может быть бесплатно, но в равной степени может использоваться другим приложением. Это критическая часть управления памятью, где ошибка будет иметь непредсказуемые (а иногда и неповторимые) последствия.

Также вы можете использовать динамическое распределение памяти, когда вам нужно определить огромный массив, например int [10000]. Вы не можете просто положить его в стек, потому что тогда … hm … вы получите переполнение стека.

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

(Я пишу, потому что я чувствую, что ответы пока не совсем совпадают).

Причина, по которой вам стоит обратить внимание на управление памятью, – это когда у вас есть проблема / решение, требующее создания сложных структур. (Если ваши программы разбиваются, если вы выделяете много места в стек сразу, это ошибка.) Как правило, первой структурой данных, которую вам нужно будет узнать, является какой-то список . Вот один связанный, с моей головы:

 typedef struct listelem { struct listelem *next; void *data;} listelem; listelem * create(void * data) { listelem *p = calloc(1, sizeof(listelem)); if(p) p->data = data; return p; } listelem * delete(listelem * p) { listelem next = p->next; free(p); return next; } void deleteall(listelem * p) { while(p) p = delete(p); } void foreach(listelem * p, void (*fun)(void *data) ) { for( ; p != NULL; p = p->next) fun(p->data); } listelem * merge(listelem *p, listelem *q) { while(p != NULL && p->next != NULL) p = p->next; if(p) { p->next = q; return p; } else return q; } 

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

  • Используя тот факт, что malloc гарантирован (по языковому стандарту), чтобы вернуть указатель, делящийся на 4,
  • выделяя дополнительное пространство для какой-то зловещей цели,
  • создание пула памяти s ..

Получите хороший отладчик … Удачи!

@ Euro Micelli

Один отрицательный для добавления – это то, что указатели на стек больше недействительны, когда функция возвращается, поэтому вы не можете вернуть указатель на переменную стека из функции. Это распространенная ошибка и основная причина, по которой вы не можете обойтись только с помощью переменных стека. Если вашей функции нужно вернуть указатель, вам необходимо выполнить malloc и управлять памятью.

@ Тед Персиваль :
… вам не нужно указывать возвращаемое значение malloc ().

Вы, конечно, правы. Я считаю, что это всегда было правдой, хотя у меня нет копии K & R для проверки.

Мне не нравится много неявных преобразований в C, поэтому я склонен использовать броски, чтобы сделать «волшебство» более заметным. Иногда это помогает читабельности, иногда это не так, и иногда это приводит к тому, что компилятор обнаруживает тихую ошибку. Тем не менее, у меня нет сильного мнения об этом, так или иначе.

Это особенно вероятно, если ваш компилятор понимает комментарии в стиле C ++.

Да … ты меня поймал. Я трачу намного больше времени на C ++, чем C. Спасибо, что заметили это.

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

а. Вы хотите, чтобы переменная переполнила функции, и вы не хотите иметь глобальную переменную. например:

 struct pair {
    int val;
    struct pair * next;
 }

 struct pair * new_pair (int val) {
    struct pair * np = malloc (sizeof (struct pair));
    np-> val = val;
    np-> next = NULL;
    return np;
 }

б. вы хотите иметь динамически выделенную память. Наиболее распространенным примером является массив без фиксированной длины:

 int * my_special_array;
 my_special_array = malloc (sizeof (int) * number_of_element);
 для (i = 0; i

с. Вы хотите сделать что-то ДЕЙСТВИТЕЛЬНО грязное. Например, я хотел бы, чтобы структура представляла много видов данных, и мне не нравится union (union выглядит soooo messy):

struct data { int data_type; long data_in_mem; }; struct animal {/ * something * /}; struct person {/ * некоторые другие вещи * /}; struct animal * read_animal (); struct person * read_person (); / * В основной * / образец данных структуры; sampe.data_type = input_type; Переключатель (input_type) { case DATA_PERSON: sample.data_in_mem = read_person (); ломать; case DATA_ANIMAL: sample.data_in_mem = read_animal (); по умолчанию: printf («О, ох, я предупреждаю вас, что снова и я позову вашу ОС»); }

Видишь, достаточно длинного значения, чтобы удержать НИЧЕГО. Просто не забудьте освободить его, или вы пожалеете. Это один из моих любимых трюков, чтобы повеселиться в C: D.

Однако, как правило, вы хотели бы держаться подальше от своих любимых трюков (T___T). Вы рано или поздно порвете свою ОС, если будете использовать их слишком часто. Пока вы не используете * alloc и free, можно с уверенностью сказать, что вы все еще девственны, и что код по-прежнему выглядит неплохо.

Конечно. Если вы создаете объект, который существует за пределами области действия, в которой вы его используете. Вот надуманный пример (помните, что мой синтаксис выключен, мой C ржавый, но этот пример будет по-прежнему иллюстрировать концепцию):

 class MyClass { SomeOtherClass *myObject; public MyClass() { //The object is created when the class is constructed myObject = (SomeOtherClass*)malloc(sizeof(myObject)); } public ~MyClass() { //The class is destructed //If you don't free the object here, you leak memory free(myObject); } public void SomeMemberFunction() { //Some use of the object myObject->SomeOperation(); } }; 

В этом примере я использую объект типа SomeOtherClass во время жизни MyClass. Объект SomeOtherClass используется в нескольких функциях, поэтому я динамически выделял память: объект SomeOtherClass создается, когда MyClass создается, используется несколько раз в течение всего срока службы объекта, а затем освобождается после освобождения MyClass.

Очевидно, что если бы это был реальный код, не было бы причин (кроме возможного потребления памяти стека) для создания myObject таким образом, но этот тип создания / уничтожения объекта становится полезным, когда у вас много объектов и вы хотите точно контролировать когда они создаются и уничтожаются (так что ваше приложение не всасывает 1 ГБ ОЗУ на протяжении всего его жизненного цикла, например), а в среде Windowed это в значительной степени обязательно, поскольку объекты, которые вы создаете (кнопки, скажем) , должны существовать хорошо вне сферы видимости какой-либо конкретной функции (или даже classа).

  • java.lang.OutOfMemoryError: размер растрового изображения превышает бюджет VM - Android
  • Возврат локальных данных из функций в C и C ++ с помощью указателя
  • Будет ли `char` всегда всегда - всегда иметь 8 бит?
  • Как найти утечку памяти Java
  • java.lang.OutOfMemoryError: пространство кучи Java
  • Невозможно освободить память, занятую байтами.
  • Общая память между двумя процессами (приложениями)
  • Как обращаться с realloc при сбое из-за памяти?
  • Как обойти утечку памяти в элементе управления .NET Webbrowser?
  • Как реализуется malloc () внутри?
  • Массивы на Java и то, как они хранятся в памяти
  • Interesting Posts

    Как реализовать данные в реальном времени для веб-страницы

    Windows находит все файлы в каталоге

    Инициализация вектора ublas из массива C

    Остановить chkdsk, когда Windows 7 на одном диске и Windows 8 на другом

    Почему моя материнская плата не будет работать с двумя процессорами?

    Есть ли причина повторного использования C # переменной в foreach?

    Отправка сообщения конкретному пользователю в Spring Websocket

    Rails вложенная форма с has_many: через, как редактировать атрибуты модели соединения?

    конфликт версии mscorlib во время сборки

    Разбор номера из экспоненциального обозначения

    Формат дискового хранилища, который будет использоваться всеми ОС

    Отсутствующие элементы пользовательского интерфейса в XCode 6

    Как преобразовать подкаст в аудиокнигу / музыкальный трек в iTunes?

    Планирование JavaBean с помощью свойств JavaFX

    Могу ли я использовать два разных интернет-соединения для большей скорости?

    Давайте будем гением компьютера.