Подтвердить что ты не робот

Распределение памяти/освобождение?

В последнее время я смотрю на распределение памяти, и я немного смущен об основах. Мне не удалось обернуть голову про простые вещи. Что означает выделение памяти? Что происходит? Я бы оценил ответы на любой из этих вопросов:

  • Где находится "память" , которая выделяется?
  • Что это за "память" ? Пространство в массиве? Или что-то другое?
  • Что происходит, когда эта "память" распределяется?
  • Что происходит, когда память освобождается?
  • Это также действительно помогло бы мне, если бы кто-нибудь мог ответить на то, что malloc делает в этих С++-строках:

    char* x; 
    x = (char*) malloc (8);
    

Спасибо.

4b9b3361

Ответ 1

Модель памяти

Стандарт С++ имеет модель памяти. Он пытается моделировать память в компьютерной системе в общем виде. Стандарт определяет, что байт является блоком памяти в модели памяти и что память состоит из байтов (§1.7):

Основным блоком памяти в модели памяти С++ является байт. [...] Память, доступная для программы на С++, состоит из одной или нескольких последовательностей смежных байтов.

Объектная модель

Стандарт всегда предоставляет объектную модель. Это указывает, что объект является областью хранения (поэтому он состоит из байтов и находится в памяти) (§ 1.8):

Конструкции в программе на С++ создают, уничтожают, ссылаются, обрабатывают и обрабатывают объекты. Объект - это область хранения.

Итак, идем. Память - это место хранения объектов. Чтобы сохранить объект в памяти, необходимо выделить требуемую область хранения.

Функции распределения и освобождения

Стандарт предоставляет две неявно объявленные глобальные функции выделения областей:

void* operator new(std::size_t);
void* operator new[](std::size_t);

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

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

Он также определяет две соответствующие функции освобождения:

void operator delete(void*);
void operator delete[](void*);

которые определены для освобождения ранее выделенного хранилища (§3.7.4.2):

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

new и delete

Как правило, вам не нужно использовать функции распределения и освобождения напрямую, потому что они дают вам неинициализированную память. Вместо этого в С++ вы должны использовать new и delete для динамического выделения объектов. Новое выражение получает хранилище для запрашиваемого типа, используя одну из вышеперечисленных функций распределения, а затем инициализирует объект каким-либо образом. Например, new int() выделит пространство для объекта int, а затем инициализирует его равным 0. См. §5.3.4:

Новое выражение получает хранилище для объекта, вызывая функцию распределения (3.7.4.1).

[...]

Новое выражение, создающее объект типа T, инициализирует этот объект [...]

В обратном направлении delete вызовет деструктор объекта (если он есть), а затем освободит память (§5.3.5):

Если значение операнда выражения-удаления не является значением нулевого указателя, выражение-delete будет вызывать деструктор (если есть) для объекта или элементов удаляемого массива.

[...]

Если значение операнда выражения-удаления не является значением нулевого указателя, выражение-выражение вызовет функцию освобождения (3.7.4.2).

Другие распределения

Однако это не единственные способы выделения или освобождения хранилища. Многие конструкции языка неявно требуют выделения хранилища. Например, определение объекта, например int a;, также требует хранения (§7):

Определение приводит к резервированию соответствующего объема хранилища и любой соответствующей инициализации (8.5).

Стандартная библиотека C: malloc и free

Кроме того, заголовок <cstdlib> содержит содержимое стандартной библиотеки stdlib.h C, которая включает в себя функции malloc и free. Они также определяются стандартом C, чтобы выделять и освобождать память, подобно функциям распределения и освобождения, определенным стандартом С++. Здесь определение malloc (C99 §7.20.3.3):

void *malloc(size_t size);
Описание
Функция malloc выделяет пространство для объекта, размер которого указан size и значение которого неопределенно. Возврат
Функция malloc возвращает либо нулевой указатель, либо указатель на выделенное пространство.

И определение free (C99 §7.20.3.2):

void free(void *ptr);
Описание
Функция free вызывает освобождение пространства, на которое указывает ptr, т.е. Сделанное доступный для дальнейшего распределения. Если ptr - нулевой указатель, никакого действия не происходит. В противном случае, если аргумент не соответствует указателю, ранее возвращенному функцией calloc, malloc или realloc, или если пространство было освобождено вызовом free или realloc, поведение undefined.

Однако никогда не бывает хорошего оправдания использовать malloc и free в С++. Как описано выше, у С++ есть свои альтернативы.


Ответы на вопросы

Чтобы ответить на ваши вопросы напрямую:

  • Где находится "память" , которая выделяется?

    Стандарту С++ все равно. Он просто говорит, что у программы есть память, состоящая из байтов. Эта память может быть выделена.

  • Что это за "память" ? Пространство в массиве? Или что-то другое?

    Что касается стандарта, то память представляет собой всего лишь последовательность байтов. Это целенаправленно очень общее, поскольку стандарт только пытается моделировать типичные компьютерные системы. Вы можете, по большей части, подумать об этом как о модели ОЗУ вашего компьютера.

  • Что происходит, когда эта "память" распределяется?

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

  • Что происходит, когда память освобождается?

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

  • Это также действительно помогло бы мне, если бы кто-нибудь мог ответить на то, что malloc делает в этих С++-строках:

    char* x; 
    x = (char*) malloc (8);
    

    Здесь malloc просто выделяет 8 байт памяти. Возвращаемый указатель передается в char* и сохраняется в x.

Ответ 2

1) Где находится "память" , которая выделяется?

Это полностью отличается от вашей операционной системы, среды программирования (gcc vs Visual С++ vs Borland С++ и всего остального), компьютера, доступной памяти и т.д. В общем, память выделяется из так называемой кучи, области память просто ждет вас для использования. Обычно он будет использовать вашу оперативную память. Но всегда есть исключения. По большей части, пока это дает нам память, откуда она исходит, это не вызывает большой озабоченности. Существуют специальные типы памяти, такие как виртуальная память, которые могут или не могут быть фактически в ОЗУ в любой момент времени и могут быть перенесены на ваш жесткий диск (или подобное запоминающее устройство), если у вас закончилась реальная память. Полное объяснение будет очень длинным!

2) Что это за "память" ? Пространство в массиве? Или что-то еще?

Память - это, как правило, ОЗУ на вашем компьютере. Если полезно думать о памяти как о гигантском "массиве", то он работает как один, а затем рассматривает его как тонну байтов (8-битные значения, как и значения unsigned char). Он начинается с индекса 0 в нижней части памяти. Однако, как и раньше, здесь существует множество исключений, и некоторые части памяти могут быть сопоставлены с оборудованием или вообще не существуют вообще!

3) Что происходит именно тогда, когда эта "память" получает выделение?

В любой момент времени должно быть (мы действительно надеемся!) некоторые из них доступны для размещения программного обеспечения. Как он распределяется, он сильно зависит от системы. В общем случае выделяется область памяти, распределитель помечает ее как используемую, а затем вам указывается указатель, который сообщает программе, где находится вся ваша системная память, в которой находится память. В вашем примере программа найдет последовательный блок из 8 байтов (char) и вернет указатель туда, где он нашел этот блок после того, как он пометит его как "в использовании".

4) Что происходит, когда память освобождается?

Система отмечает эту память как доступную для использования снова. Это невероятно сложно, потому что это часто вызывает дыры в памяти. Выделите 8 байт, затем еще 8 байт, затем освободите первые 8 байтов, и у вас есть отверстие. Есть целые книги, написанные по обработке освобождения памяти, распределению памяти и т.д. Так что, надеюсь, короткого ответа будет достаточно!

5) Это также действительно помогло бы мне, если бы кто-то мог ответить на то, что malloc делает в этих С++-строках:

ДЕЙСТВИТЕЛЬНО грубо и предполагая это в функции (кстати, никогда не делайте этого, потому что он не освобождает память и не вызывает утечку памяти):

void mysample() {
  char *x; // 1
  x = (char *) malloc(8); // 2
}

1) Это указатель, зарезервированный в локальном пространстве стека. Он не инициализирован, поэтому он указывает на то, что в нем был бит памяти.

2) Он вызывает malloc с параметром 8. Приведение просто позволяет C/С++ знать, что вы намерены считать его (char *), потому что он возвращает (void *), что означает, что он не применяет тип. Затем полученный указатель сохраняется в вашей переменной x.

В очень грубой сборке x86 32bit это будет выглядеть нечетко как

PROC mysample:
  ; char *x;
  x = DWord Ptr [ebp - 4]
  enter 4, 0   ; Enter and preserve 4 bytes for use with 

  ; x = (char *) malloc(8);
  push 8       ; We're using 8 for Malloc
  call malloc  ; Call malloc to do it thing
  sub esp, 4   ; Correct the stack
  mov x, eax   ; Store the return value, which is in EAX, into x

  leave
  ret

Фактическое распределение неопределенно описано в пункте 3. Malloc обычно просто вызывает системную функцию для этого, которая обрабатывает все остальное, и, как и все остальное, здесь сильно отличается от ОС к ОС, от системы к системе и т.д.

Ответ 3

1. Где выделяется "память" ?

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

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

Пример 1:

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

Пример 2:

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

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

2. Что это за "память" ? Пространство в массиве? Или что-то еще?

В примере 1 это нечто вроде пространства в массиве: возвращенный адрес идентифицирует адресную часть физической ОЗУ. Даже здесь адреса ОЗУ не обязательно являются плоскими или смежными - некоторые адреса могут быть зарезервированы для ПЗУ или для портов ввода/вывода.

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

3. Что происходит именно тогда, когда выделена эта "память" ?

В общем случае возвращается указатель, который вы можете использовать для хранения как можно большего количества байтов. В обоих случаях оператор malloc или new выполняет некоторую служебную работу, чтобы отслеживать, какие части вашего пространства адресов используются и которые являются бесплатными.

4. Что происходит, когда память освобождается?

В общем случае free или delete будут выполнять некоторую домашнюю работу, чтобы они знали, что память доступна для перераспределения.

Это также действительно помогло бы мне, если бы кто-нибудь мог ответить на то, что malloc делает в этих С++-строках:

char* x; 
x = (char*) malloc (8);

Он возвращает указатель, который является либо NULL (если он не может найти 8 байтов, которые вы хотите), или некоторое значение, отличное от NULL.

Единственное, что можно с пользой сказать об этом значении, отличном от NULL, это то, что:

  • он легален (и безопасен) для доступа к каждому из этих 8 байтов x[0]..x[7],
  • это незаконно (поведение undefined) для доступа к x[-1] или x[8] или фактически любому x[i], если 0 <= i <= 7
  • правомерно сравнивать любой из x, x+1, ..., x+8 (хотя вы не можете разыгрывать последний из них)
  • Если ваша платформа/оборудование/все есть какие-либо ограничения на то, где вы можете хранить данные в памяти, тогда x встречает их

Ответ 4

Чтобы выделить память, нужно запросить операционную систему для памяти. Это означает, что сама программа запрашивает "пространство" в ОЗУ, только когда это необходимо. Например, если вы хотите использовать массив, но вы не знаете его размер до запуска программы, вы можете сделать две вещи: - объявить и массив [x] с x, выделенный вами, произвольный long. Например, 100. Но как насчет того, нужна ли вашей программе всего 20 элементов? Вы зря теряете память. - тогда вы можете malloc массива элементов x только тогда, когда он знает правильный размер x. Программы в памяти разделены на 4 сегмента: -stack (требуется для вызова функций) -код (исполняемый код бибарида) - данные (глобальные переменные/данные) - куча, в этом сегменте вы найдете выделенную память. Когда вы решите, что вам больше не нужна выделенная память, вы возвращаете ее в операционную систему.

Если вы хотите выделить и массив из 10 целых чисел, вы делаете:

int * array = (int *) malloc (sizeof (int) * 10)

И затем вы возвращаете его обратно бесплатно (массив)