Динамическая память. Управляющие конструкции языка Си
Последнее обновление: 28.05.2017
При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:
Double numbers = {1.0, 2.0, 3.0, 4.0, 5.0};
Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.
Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h :
malloc() . Имеет прототип
Void *malloc(unsigned s);
Выделяет память длиной в s байт и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
calloc() . Имеет прототип
Void *calloc(unsigned n, unsigned m);
Выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
realloc() . Имеет прототип
Void *realloc(void *bl, unsigned ns);
Изменяет размер ранее выделенного блока памяти, на начало которого указывает указатель bl, до размера в ns байт. Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc
free() . Имеет прототип
Void *free(void *bl);
Освобождает ранее выделенный блок памяти, на начало которого указывает указатель bl.
Если мы не используем эту функцию, то динамическая память все равно освободится автоматически при завершении работы программы. Однако все же хорошей практикой является вызов функции free() , который позволяет как можно раньше освободить память.
Рассмотрим применение функций на простой задаче. Длина массива неизвестна и вводится во время выполнения программы пользователем, и также значения всех элементов вводятся пользователем:
#include Консольный вывод программы: Size of array=5
block=23
block=-4
block=0
block=17
block=81
23 -4 0 17 81 Здесь для управления памятью для массива определен указатель block типа int
. Количество элементов массива заранее неизвестно,
оно представлено переменной n. Вначале пользователь вводит количество элементов, которое попадает в переменную n. После этого необходимо выделить память для данного количества
элементов. Для выделения памяти здесь мы могли бы воспользоваться любой из трех вышеописанных функций: malloc, calloc, realloc.
Но конкретно в данной ситуации воспользуемся функцией malloc
: Block = malloc(n * sizeof(int));
Прежде всего надо отметить, что все три выше упомянутые функции для универсальности возвращаемого значения в качестве результата возвращают указатель типа
void *
. Но в нашем случае создается массив типа int, для управления которым используется указатель типа int *
,
поэтому выполняется неявное приведение результата функции malloc к типу int *
. В саму функцию malloc передается количество байтов для выделяемого блока. Это количество подсчитать довольно просто: достаточно умножить количество элементов на размер одного элемента
n * sizeof(int) . После выполнения всех действий память освобождается с помощью функции free()
: Free(block);
Важно, что после выполнения этой функции мы уже не сможем использовать массив, например, вывести его значения на консоль: Free(block);
for(int i=0;i И если мы попытаемся это сделать, то получим неопределенные значения. Вместо функции malloc аналогичным образом мы могли бы использовать функцию calloc()
, которая принимает количество элементов и размер одного элемента: Block = calloc(n, sizeof(int));
Либо также можно было бы использовать функцию realloc()
: Int *block = NULL;
block = realloc (block, n * sizeof(int));
При использовании realloc желательно (в некоторых средах, например, в Visual Studio, обязательно) инициализировать указатель хотя бы значением NULL. Но в целом все три вызова в данном случае имели бы аналогичное действие: Block = malloc(n * sizeof(int));
block = calloc(n, sizeof(int));
block = realloc (block, n * sizeof(int));
Теперь рассмотрим более сложную задачу - динамическое выделение памяти для двухмерного массива:
#include Переменная table представляет указатель на массив указателей типа int*
. Каждый указатель table[i] в этом массиве представляет указатель на подмассив элементов типа int
, то есть отдельные строки таблицы.
А переменная table фактически представляет указатель на массив указателей на строки таблицы. Для хранения количества элементов в каждом подмассиве определяется указатель rows типа int
. Фактически он хранит количество столбцов для каждой строки таблицы. Сначала вводится количество строк в переменную rowscount . Количество строк - это количество указателей в массиве, на который указывает указатель table .
И кроме того, количество строк - это количество элементов в динамическом массиве, на который указывает указатель rows . Поэтому вначале необходимо для всех этих массивов
выделить память: Table = calloc(rowscount, sizeof(int*));
rows = malloc(sizeof(int)*rowscount);
Далее в цикле осуществляется ввод количества столбцов для каждый строки. Введенное значение попадает в массив rows. И в соответствии с введенным значением для каждой строки выделяется необходимый размер памяти: Scanf("%d", &rows[i]);
table[i] = calloc(rows[i], sizeof(int));
Затем производится ввод элементов для каждой строки. В конце работы программы при выводе происходит освобождение памяти. В программе память выделяется для строк таблицы, поэтому эту память надо освободить: Free(table[i]);
И кроме того, освобождается память, выделенная для указателей table и rows: Free(table);
free(rows);
Консольный вывод программы: Rows count=2
Columns count for 1=3
table=1
table=2
table=3
Columns count for 2=2
table=4
table=5
1 2 3
4 5 Очень часто возникают задачи обработки массивов данных, размерность которых заранее неизвестна. В этом случае возможно использование одного из двух подходов: Для использования функций динамического выделения памяти необходимо описать указатель , представляющий собой начальный адрес хранения элементов массива. int
*p; // указатель на тип int
Начальный адрес статического массива определяется компилятором в момент его объявления и не может быть изменен. Для динамического массива начальный адрес присваивается объявленному указателю на массив в процессе выполнения программы. Функции динамического выделения памяти находят в оперативной памяти непрерывный участок требуемой длины и возвращают начальный адрес этого участка. Функции динамического распределения памяти: void
* malloc(РазмерМассиваВБайтах); Для использования функций динамического распределения памяти необходимо подключение библиотеки #include
Поскольку обе представленные функции в качестве возвращаемого значения имеют указатель на пустой тип void
, требуется явное приведение типа возвращаемого значения. Для определения размера массива в байтах, используемого в качестве аргумента функции malloc()
требуется количество элементов умножить на размер одного элемента. Поскольку элементами массива могут быть как данные простых типов, так и составных типов (например, структуры), для точного определения размера элемента в общем случае рекомендуется использование функции int
sizeof
(тип); Память, динамически выделенная с использованием функций calloc(), malloc()
, может быть освобождена с использованием функции free(указатель);
«Правилом хорошего тона» в программировании является освобождение динамически выделенной памяти в случае отсутствия ее дальнейшего использования. Однако если динамически выделенная память не освобождается явным образом, она будет освобождена по завершении выполнения программы. Форма обращения к элементам массива с помощью указателей имеет следующий вид: int
a, *p; // описываем статический массив и указатель
Пример на Си
: Организация динамического одномерного массива и ввод его элементов. 1 Пусть требуется разместить в динамической памяти матрицу, содержащую n
строк и m
столбцов. Двумерная матрица будет располагаться в оперативной памяти в форме ленты, состоящей из элементов строк. При этом индекс любого элемента двумерной матрицы можно получить по формуле index = i*m+j;
где i
- номер текущей строки; j
- номер текущего столбца. Рассмотрим матрицу 3x4 (см. рис.) index = 1*4+2=6
Объем памяти, требуемый для размещения двумерного массива, определится как n·m·(размер элемента)
Однако поскольку при таком объявлении компилятору явно не указывается количество элементов в строке и столбце двумерного массива, традиционное обращение к элементу путем указания индекса строки и индекса столбца является некорректным: a[i][j]
- некорректно. Правильное обращение к элементу с использованием указателя будет выглядеть как *(p+i*m+j)
, Пример на Си
1 #define
_CRT_SECURE_NO_WARNINGS
Результат выполнения Возможен также другой способ динамического выделения памяти под двумерный массив - с использованием массива указателей. Для этого необходимо: Графически такой способ выделения памяти можно представить следующим образом. 1 #define
_CRT_SECURE_NO_WARNINGS
Результат выполнения программы аналогичен предыдущему случаю. С помощью динамического выделения памяти под указатели строк можно размещать свободные массивы. Свободным
называется двухмерный массив (матрица), размер строк которого может быть различным. Преимущество использования свободного массива заключается в том, что не требуется отводить память компьютера с запасом для размещения строки максимально возможной длины. Фактически свободный массив представляет собой одномерный массив указателей на одномерные массивы данных. Для размещения в оперативной памяти матрицы со строками разной длины необходимо ввести дополнительный массив m
, в котором будут храниться размеры строк. Пример на Си
1 #define
_CRT_SECURE_NO_WARNINGS
Если размер выделяемой памяти нельзя задать заранее, например при вводе последовательности значений до определенной команды, то для увеличения размера массива при вводе следующего значения необходимо выполнить следующие действия: Все перечисленные выше действия (кроме последнего) выполняет функция void
* realloc (void
* ptr, size_t size); Размер блока памяти, на который ссылается параметр ptr
изменяется на size
байтов. Блок памяти может уменьшаться или увеличиваться в размере. Содержимое блока памяти сохраняется даже если новый блок имеет меньший размер, чем старый. Но отбрасываются те данные, которые выходят за рамки нового блока. Если новый блок памяти больше старого, то содержимое вновь выделенной памяти будет неопределенным. Прежде чем углубиться в объектно-ориентированную разработку, нам придется сделать
небольшое отступление о работе с памятью в программе на С++. Мы не сможем написать
сколько-нибудь сложную программу, не умея выделять память во время выполнения
и обращаться к ней. Int ival = 1024;
заставляет компилятор выделить в памяти область, достаточную для хранения переменной
типа int, связать с этой областью имя ival и поместить туда значение 1024. Все
это делается на этапе компиляции, до выполнения программы. Int ival2 = ival + 1;
то обращаемся к значению, содержащемуся в переменной ival: прибавляем к нему
1 и инициализируем переменную ival2 этим новым значением, 1025. Каким же образом
обратиться к адресу, по которому размещена переменная? Int *pint; // указатель на объект типа int
Существует также специальная операция взятия адреса, обозначаемая символом
&. Ее результатом является адрес объекта. Следующий оператор присваивает
указателю pint адрес переменной ival: Int *pint;
pint = &ival; // pint получает значение адреса ival
Мы можем обратиться к тому объекту, адрес которого содержит pint (ival в нашем
случае), используя операцию разыменования
, называемую также косвенной
адресацией
. Эта операция обозначается символом *. Вот как можно косвенно
прибавить единицу к ival, используя ее адрес:
*pint = *pint + 1; // неявно увеличивает ival
Это выражение производит в точности те же действия, что и Ival = ival + 1; // явно увеличивает ival
В этом примере нет никакого реального смысла: использование указателя для косвенной
манипуляции переменной ival менее эффективно и менее наглядно. Мы привели этот
пример только для того, чтобы дать самое начальное представление об указателях.
В реальности указатели используют чаще всего для манипуляций с динамически размещенными
объектами. Оператор new имеет две формы. Первая форма выделяет память под единичный объект
определенного типа: Int *pint = new int(1024);
Здесь оператор new выделяет память под безымянный объект типа int, инициализирует
его значением 1024 и возвращает адрес созданного объекта. Этот адрес используется
для инициализации указателя pint. Все действия над таким безымянным объектом
производятся путем разыменовывания данного указателя, т.к. явно манипулировать
динамическим объектом невозможно. Int *pia = new int;
В этом примере память выделяется под массив из четырех элементов типа int.
К сожалению, данная форма оператора new не позволяет инициализировать элементы
массива.
// освобождение единичного объекта
delete pint;
// освобождение массива
delete pia;
Что случится, если мы забудем освободить выделенную память? Память будет расходоваться
впустую, она окажется неиспользуемой, однако возвратить ее системе нельзя, поскольку
у нас нет указателя на нее. Такое явление получило специальное название утечка
памяти
. В конце концов программа аварийно завершится из-за нехватки памяти
(если, конечно, она будет работать достаточно долго). Небольшая утечка трудно
поддается обнаружению, но существуют утилиты, помогающие это сделать. Объясните разницу между четырьмя объектами:
(a) int ival = 1024;
(b) int *pi = &ival;
(c) int *pi2 = new int(1024);
(d) int *pi3 = new int;
Что делает следующий фрагмент кода? В чем состоит логическая ошибка? (Отметим,
что операция взятия индекса () правильно применена к указателю pia. Объяснение
этому факту можно найти в разделе 3.9.2.) Int *pi = new int(10);
int *pia = new int;
Работа с динамической памятью зачастую является узким местом во многих алгоритмах, если не применять специальные ухищрения. В статье я рассмотрю парочку таких техник. Примеры в статье отличаются (например, от этого) тем, что используется перегрузка операторов new и delete и за счёт этого синтаксические конструкции будут минималистичными, а переделка программы - простой. Также описаны подводные камни, найденные в процессе (конечно, гуру, читавшие стандарт от корки до корки, не удивятся). Напишем простые тесты для C++ и C# (C# известен прекрасным менеджером памяти, который делит объекты по поколениям, использует разные пулы для объектов разных размеров и т.п.). Class Node {
public:
Node* next;
};
// ...
for (int i = 0; i < 10000000; i++) {
Node* v = new Node();
}
Class Node
{
public Node next;
}
// ...
for (int l = 0; l < 10000000; l++)
{
var v = new Node();
}
Несмотря на всю «сферично-вакуумность» примера, разница по времени получилась в 10 раз (62 ms против 650 ms). Кроме того, c#-пример закончен, а по правилам хорошего тона в c++ выделенные объекты надо удалить, что ещё больше увеличит отрыв (до 2580 ms). Поскольку стоит задача минимального вмешательства в программу, всё что можно будет сделать, это добавить примесь BlockAlloc к классу Node: Прежде всего нам понадобится пул больших блоков (страниц), которые забираем у ОС или C-runtime. Его можно организовать поверх функций malloc и free, но для большей эффективности (чтобы пропустить лишний уровень абстракции), используем VirtualAlloc/VirtualFree. Эти функции выделяют память блоками, кратными 4K, а также резервируют адресное пространство процесса блоками, кратными 64K. Одновременно указывая опции commit и reserve, мы перескакиваем ещё один уровень абстракции, резервируя адресное пространство и выделяя страницы памяти одним вызовом. Класс PagePool
inline size_t align(size_t x, size_t a) { return ((x-1) | (a-1)) + 1; }
//#define align(x, a) ((((x)-1) | ((a)-1)) + 1)
template Затем организуем пул блоков заданного размера Класс BlockPool
template Комментарием // todo: lock(this)
помечены места, которые требуют межпоточной синхронизации (например, используйте EnterCriticalSection или boost::mutex). Объясню, почему при «форматировании» страницы не ипользуется абстракция FreeBlock для добавления блока в пул. Если бы было написано что-то вроде For (size_t i = 0; i < PageSize; i += BlockSize) FreeBlock((char*)tmp+i);
То страница по принципу FIFO оказалась бы размеченной «наоборот»: Несколько блоков, затребованных из пула подряд, имели бы убывающие адреса. А процессор не любит ходить назад, от этого у него ломается Prefetch (UPD
: Не актуально для современных процессоров). Если же делать разметку в цикле Теперь, когда приготовления сделаны, можно описать класс-примесь. Объясню, зачем нужны проверки if (s != sizeof(T))
Готово. Наследуем Node от BlockAlloc и заново проводим тест. Часто ли попадаются классы, которые хранят в себе массу различных дочерних объектов, таких, что время жизни последних не дольше времени жизни родителя? Например, это может быть класс XmlDocument, наполненный классами Node и Attribute, а также c-строками (char*), взятыми из текста внутри нод. Или список файлов и каталогов в файловом менеджере, загружаемых один раз при перечитывании каталога и больше не меняющихся. Как было показано во введении, delete обходится дороже, чем new. Идея второй части статьи в том, чтобы память под дочерние объекты выделять в большом блоке, связанном с Parent-объектом. При удалении parent-объекта у дочерних будут, как обычно, вызваны деструкторы, но память возвращать не потребуется - она освободиться одним большим блоком. Создадим класс PointerBumpAllocator, который умеет откусывать от большого блока куски разных размеров и выделять новый большой блок, когда старый будет исчерпан. Класс PointerBumpAllocator
template Наконец, опишем примесь ChildObject с перегруженными new и delete, обращающимися к заданному аллокатору: Template В этом случае кроме добавления примеси в child-класс необходимо будет также исправить все вызовы new (или воспользоваться паттерном «фабрика»). Синтаксис оператора new будет следующим: New (… параметры для оператора…) ChildObject (… параметры конструктора…) Для удобства я задал два оператора new, принимающих A& или A*. Для вызова delete не предусмотрен специальный синтаксис, компилятор вызовет стандартный delete (отмеченный *1), независимо от того, какой из операторов new был использован для создания объекта. То есть, синтаксис delete обычный: Если же в конструкторе ChildObject (или его наследника) происходит исключение, вызывается delete с сигнатурой, соответствующей сигнатуре оператора new, использованном при создании этого объекта (первый параметр size_t будет заменён на void*). Размешение оператора new в секции private защищает от вызова new без указания аллокатора. Приведу законченный пример использования пары Allocator-ChildObject: Пример
class XmlDocument: public DefaultAllocator
{
public:
~XmlDocument() {
for (vector Заключение. Статья была написана 1.5 года назад для песочницы, но увы, не понравилась модератору. Мы открыли для себя возможности динамического выделения памяти. Что это значит? Это значит то, что при динамическом выделении памяти, память резервируется не на этапе компиляции а на этапе выполнения программы. И это дает нам возможность выделять память более эффективно, в основном это касается массивов. С динамическим выделением память, нам нет необходимости заранее задавать размер массива, тем более, что не всегда известно, какой размер должен быть у массива. Далее рассмотрим каким же образом можно выделять память. Функция malloc() определена в заголовочном файле stdlib.h , она используется для инициализации указателей необходимым объемом памяти. Память выделяется из сектора оперативной памяти доступного для любых программ, выполняемых на данной машине. Аргументом является количество байт памяти, которую необходимо выделить, возвращает функция — указатель на выделенный блок в памяти. Функция malloc() работает также как и любая другая функция, ничего нового. Так как различные типы данных имеют разные требования к памяти, мы как-то должны научиться получить размер в байтах для данных разного типа. Например, нам нужен участок памяти под массив значений типа int — это один размер памяти, а если нам нужно выделить память под массив того же размера, но уже типа char — это другой размер. Поэтому нужно как-то вычислять размер памяти. Это может быть сделано с помощью операции sizeof() , которая принимает выражение и возвращает его размер. Например, sizeof(int) вернет количество байтов, необходимых для хранения значения типа int . Рассмотрим пример:
#include В этом примере, в строке 3
указателю ptrVar присваивается адрес на участок памяти, размер которого соответствует типу данных int . Автоматически, этот участок памяти становится недоступным для других программ. А это значит, что после того, как выделенная память станет ненужной, её нужно явно высвободить. Если же память не будет явно высвобождена, то по завершению работы программы, память так и не освободится для операционной системы, это называется утечкой памяти. Также можно определять размер выделяемой памяти, которую нужно выделить передавая пустой указатель, вот пример: Int *ptrVar = malloc(sizeof(*ptrVar));
Что здесь происходит? Операция sizeof(*ptrVar) оценит размер участка памяти, на который ссылается указатель. Так как ptrVar является указателем на участок памяти типа int , то sizeof() вернет размер целого числа. То есть, по сути, по первой части определения указателя, вычисляется размер для второй части. Так зачем же это нам надо? Это может понадобиться, если вдруг необходимо поменять определение указателя, int , например, на float и тогда, нам не нужно менять тип данных в двух частях определения указателя. Достаточно будет того, что мы поменяем первую часть: Float *ptrVar = malloc(sizeof(*ptrVar));
Как видите, в такой записи есть одна очень сильная сторона, мы не должны вызывать функцию malloc() с использованием sizeof(float) . Вместо этого мы передали в malloc() указатель на тип float , в таком случае, размер выделяемой памяти автоматически определится сам! Особенно это пригодится, если выделять память потребуется далеко от определения указателя: Float *ptrVar;
/* .
.
.
сто строк кода */
.
.
.
ptrVar = malloc(sizeof(*ptrVar));
Если бы вы использовали конструкцию выделения памяти с операцией sizeof() , то вам бы пришлось находить в коде определение указателя, смотреть его тип данных и уже потом вы бы смогли правильно выделить память. Высвобождение памяти выполняется с помощью функции free() . Вот пример: Free(ptrVar);
После освобождения памяти, хорошей практикой является сброс указателя в нуль, то есть присвоить *ptrVar = 0 . Если указателю присвоить 0, указатель становится нулевым, другими словами, он уже никуда не указывает. Всегда после высвобождения памяти, присваивайте указателю 0, в противном случае, даже после высвобождения памяти, указатель все равно на неё указывает, а значит вы случайно можете нанести вред другим программам, которые, возможно будут использовать эту память, но вы даже ничего об этом не узнаете и будете думать, что программа работает корректно. P.S.: Всем, кто увлекается видеомонтажом может быть интересен этот редактор видео Windows 7 . Видеоредактор называется Movavi, может кто-то уже с ним знаком или даже работал с ним. С помощью этой программы на русском языке, вы легко можете добавить видео с камеры, улучшить качество и наложить красивые видео эффекты.Стандартные функции динамического выделения памяти
void
* calloc(ЧислоЭлементов, РазмерЭлементаВБайтах);
которая определяет количество байт, занимаемое элементом указанного типа.
Динамическое выделение памяти для одномерных массивов
int
b;
p = a; // присваиваем указателю начальный адрес массива
... // ввод элементов массива
b = *p; // b = a;
b = *(p+i) // b = a[i];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include
#include
#include
int
main()
{
int
*a; // указатель на массив
int
i, n;
system("chcp 1251"
);
system("cls"
);
printf("Введите размер массива: "
);
scanf("%d"
, &n);
// Выделение памяти
a = (int
*)malloc(n * sizeof
(int
));
// Ввод элементов массива
for
(i = 0; i
printf("a[%d] = "
, i);
scanf("%d"
, &a[i]);
}
// Вывод элементов массива
for
(i = 0; i
free(a);
getchar(); getchar();
return
0;
}
Результат выполнения программы:Динамическое выделение памяти для двумерных массивов
Индекс выделенного элемента определится как
где
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include
#include
#include
int
main()
{
int
*a; // указатель на массив
int
i, j, n, m;
system("chcp 1251"
);
system("cls"
);
printf("Введите количество строк: "
);
scanf("%d"
, &n);
printf();
scanf("%d"
, &m);
// Выделение памяти
a = (int
*)malloc(n*m * sizeof
(int
));
// Ввод элементов массива
for
(i = 0; i
{
for
(j = 0; j
{
scanf("%d"
, (a + i*m + j));
}
}
// Вывод элементов массива
for
(i = 0; i
{
for
(j = 0; j
{
printf("%5d "
, *(a + i*m + j));
}
printf("\n"
);
}
free(a);
getchar(); getchar();
return
0;
}
При таком способе выделения памяти компилятору явно указано количество строк и количество столбцов в массиве.
Пример на Си
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
#include
#include
int
main()
{
int
**a; // указатель на указатель на строку элементов
int
i, j, n, m;
system("chcp 1251"
);
system("cls"
);
printf("Введите количество строк: "
);
scanf("%d"
, &n);
printf("Введите количество столбцов: "
);
scanf("%d"
, &m);
// Выделение памяти под указатели на строки
// Ввод элементов массива
for
(i = 0; i
{
// Выделение памяти под хранение строк
a[i] = (int
*)malloc(m * sizeof
(int
));
for
(j = 0; j
{
printf("a[%d][%d] = "
, i, j);
scanf("%d"
, &a[i][j]);
}
}
// Вывод элементов массива
for
(i = 0; i < n; i++) // цикл по строкам
{
for
(j = 0; j < m; j++) // цикл по столбцам
{
printf("%5d "
, a[i][j]); // 5 знакомест под элемент массива
}
printf("\n"
);
}
// Очистка памяти
for
(i = 0; i < n; i++) // цикл по строкам
free(a[i]); // освобождение памяти под строку
free(a);
getchar(); getchar();
return
0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
#include
int
main()
{
int
**a;
int
i, j, n, *m;
system("chcp 1251"
);
system("cls"
);
printf("Введите количество строк: "
);
scanf("%d"
, &n);
a = (int
**)malloc(n * sizeof
(int
*));
m = (int
*)malloc(n * sizeof
(int
)); // массив кол-ва элеменов в строках массива a
// Ввод элементов массива
for
(i = 0; i
printf("Введите количество столбцов строки %d: "
, i);
scanf("%d"
, &m[i]);
a[i] = (int
*)malloc(m[i] * sizeof
(int
));
for
(j = 0; j
scanf("%d"
, &a[i][j]);
}
}
// Вывод элементов массива
for
(i = 0; i
for
(j = 0; j
printf("%3d "
, a[i][j]);
}
printf("\n"
);
}
// Освобождение памяти
for
(i = 0; i < n; i++)
{
free(a[i]);
}
free(a);
free(m);
getchar(); getchar();
return
0;
}
Результат выполненияПерераспределение памяти
if
(i>2) i -= 2;
printf("\n"
);
a = (int
*)realloc(a, i * sizeof
(int
)); // уменьшение размера массива на 2
for
(int
j = 0; j < i; j++)
printf("%d "
, a[j]);
getchar(); getchar();
return
0;
}
В С++ объекты могут быть размещены либо статически – во время компиляции, либо
динамически – во время выполнения программы, путем вызова функций из стандартной
библиотеки. Основная разница в использовании этих методов – в их эффективности
и гибкости. Статическое размещение более эффективно, так как выделение памяти
происходит до выполнения программы, однако оно гораздо менее гибко, потому что
мы должны заранее знать тип и размер размещаемого объекта. К примеру, совсем
не просто разместить содержимое некоторого текстового файла в статическом массиве
строк: нам нужно заранее знать его размер. Задачи, в которых нужно хранить и
обрабатывать заранее неизвестное число элементов, обычно требуют динамического
выделения памяти.
До сих пор во всех наших примерах использовалось статическое выделение памяти.
Скажем, определение переменной ival
С объектом ival ассоциируются две величины: собственно значение переменной,
1024 в данном случае, и адрес той области памяти, где хранится это значение.
Мы можем обращаться к любой из этих двух величин. Когда мы пишем:
С++ имеет встроенный тип “указатель”, который используется для хранения адресов
объектов. Чтобы объявить указатель, содержащий адрес переменной ival, мы должны
написать:
Основные отличия между статическим и динамическим выделением памяти таковы:
Вторая форма оператора new выделяет память под массив заданного размера, состоящий
из элементов определенного типа:
Некоторую путаницу вносит то, что обе формы оператора new возвращают одинаковый
указатель, в нашем примере это указатель на целое. И pint, и pia объявлены совершенно
одинаково, однако pint указывает на единственный объект типа int, а pia – на
первый элемент массива из четырех объектов типа int.
Когда динамический объект больше не нужен, мы должны явным образом освободить
отведенную под него память. Это делается с помощью оператора delete, имеющего,
как и new, две формы – для единичного объекта и для массива:
Наш сжатый обзор динамического выделения памяти и использования указателей,
наверное, больше породил вопросов, чем дал ответов. В разделе 8.4
затронутые проблемы будут освещены во всех подробностях. Однако мы не могли
обойтись без этого отступления, так как класс Array, который мы собираемся спроектировать
в последующих разделах, основан на использовании динамически выделяемой памяти.Упражнение 2.3
Упражнение 2.4
while (*pi < 10) {
pia[*pi] = *pi;
*pi = *pi + 1;
}
delete pi;
delete pia;
0. А нужна ли нам ручная работа с памятью?
В первую очередь проверим, насколько умный аллокатор может ускорить работу с памятью.1. Пул объектов
Очевидное решение - забрать у ОС большой блок памяти и разбить его на равные блоки размера sizeof(Node), при выделении памяти брать блок из пула, при освобождении - возвращать в пул. Пул проще всего организовать с помощью односвязного списка (стека).
class Node: public BlockAlloc
for (size_t i = PageSize-(BlockSize-(PageSize%BlockSize)); i != 0; i -= BlockSize) FreeBlock...
то цикл разметки ходил бы по адресам назад.
template
Когда они срабатывают? Тогда, когда создаётся/удаляется класс, отнаследованный от базового T.
Наследники будут пользоваться обычными new/delete, но к ним также можно примешать BlockAlloc. Таким образом, мы легко и безопасно определяем, какие классы должны пользоваться пулами, не боясь сломать что-то в программе. Множественное наследование также прекрасно работает с этой примесью.
Время теста теперь - 120 ms. В 5 раз быстрее. Но в c# аллокатор всё же лучше. Наверное, там не просто связный список. (Если же сразу после new сразу вызывать delete, и тем самым не тратить много памяти, умещая данные в кеш, получим 62 ms. Странно. В точности, как у.NET CLR, как будто он возвращает освободившиеся локальные переменные сразу в соответствующий пул, не дожидаясь GC)2. Контейнер и его пёстрое содержимое
Если аллокатор добавлен в parent-класс как член, удобнее первый вариант:
node = new(allocator) XmlNode(nodename);
Если аллокатор добавлен как предок (примесь), удобнее второй:
node = new(this) XmlNode(nodename);
delete node;
Выделение памяти в Си (функция malloc)
Высвобождение выделенной памяти
Популярное
- добротный четырехъядерный телефон, ничего кроме похвалы не заслуживающий
- AutoFaucets — автоматический сбор биткоинов с кранов (расширение в браузер) Как заработать биткоины автоматически
- Не включается планшет самсунг Samsung tab 2 не загружается
- Прошивка и перепрошивка телефона и смартфона Nokia
- Версия android 6.0 1. Обновление Андроид: как обновиться до новой версии, сделать откат? Гайд в деталях. Отключение стоковых приложений
- Как на "Асусе" сделать скриншот
- Российская система качествароскачество
- Электронные книги Установка общих параметров проекта
- IPad не удалось синхронизировать, так как сеанс синхронизации не удалось начать Айфон не синхронизируется с айтюнс что делать
- Как я получил ключ к Diablo III Beta Как я получил ключ к Diablo III Beta