Farcalloc   farrealloc работа с дальней кучей


Содержание

Задача с собеседования (аллокатор памяти)

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

Может у кого-то есть какие-то варианты? Мне что-то ничего в голову не приходит кроме как заранее поделить пул на поля с фиксированным размером N и завести битовое поле в котором храним флаги занято/свободно.. Может подкинете пару идей?

02.09.2012, 00:16

Аллокатор памяти общего назначения
Добрый день! В ВУЗе задали написать аллокатор памяти общего назначения на С++, но у меня нет ни.

Пишем аллокатор
дабы не захламлять и так уже захламленную тему про перегрузку операторов создаю новую тему.

Аллокатор в chrome
Всем привет, начал изучать исходники хрома, в аллокаторе, метод realloc должен возвращать nullptr.

Быстрый аллокатор
Собственно, необходим аллокатор для быстрого выделения памяти под мелкие объекты, совместимый со.

Пародия на стековый аллокатор
здравствуйте, решил тут чуток поиграться. сделать аллокатор чтобы данные в статическом буфере.

02.09.2012, 00:43 2 02.09.2012, 00:52 3

Да фактически ж пул и просят по идее. Выделяете pool_size * sizeof(T) байт под пул, два массива размером в pool_size: один из bool (метки занятых ячеек; думаю, это будет быстрее битовых полей), второй из T* (кеш для указателей в пуле, рассчитывается при инициализации; тоже чуть ускоряет), определяете аллокацию-деаллокацию с помощью placement new и вызова деструктора, вместе с обновлением меток.

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

02.09.2012, 00:55 4

Решение

, Александреску можно почитать на этот счет, по идее в Loki очень быстрый аллокатор для маленьких объектов ( но Александреску в своей библиотеке выделывает с С++ такое, что пытаться сделать что-то такое же просто страшно).

02.09.2012, 00:55
02.09.2012, 01:42 [ТС] 5
02.09.2012, 01:59 6
02.09.2012, 02:03 7
02.09.2012, 02:35 8
02.09.2012, 08:42 9

На самом деле, вопрос не однозначен. Ведь аллокатор это абстрактное название, никак не связанное с контейнерами STL. Поэтому не очевидно, должна ли память выделяться для одного объекта или для нескольких объектов. Уже упомянутый Александреску решил все эти проблемы при помощи классов стратегий, но тебе-то нужно один простенький вариант написать. В общем, первым делом нужно пытать задающего вопросы, чтобы не было разночтений. Это тоже входит в обязанности разработчика, потому что в документации может быть любая чушь написана, которую все по своему понять могут и реализовать вовсе не то, чего изначально думалось.

Ну а по теме могу добавить ещё реализацию аллокатора из буста. Но у Александреску более очевидная реализация и лучше у него подсмотри.

Динамическое распределение памяти.

Вопросы для собеседования

1. Что такое указатель и какие операции над указателями определены? Каким образом производится объявление, инициализация указателя? Правила адресной арифметики.

2. Каким образом определяется тип переменной — указателя? По каким правилам выполняются арифметические операции с переменными-указателями?

3. Что такое индексное выражение, приведенный индекс, для чего они нужны и как используются?

4. Какие операции с распределением памяти ЭВМ и как можно выполнить в процессе исполнения программы (динамически)?

5. Что такое функция в Си-программе? Что такое прототип и определение функции?

6. Что такое формальные и фактические аргументы функции?

7. Какие фактические параметры при вызове функции могут соответствовать формальному аргументу, являющемуся: а) идентификатором статического массива; б) указателем на функцию; в) идентификатором переменной одного из базовых типов?

8. Можно ли разные формальные аргументы функции обозначать одинаковыми идентификаторами? А фактические?

9. Перечислите все возможные способы передачи информации из программы в вызываемую функцию и обратно.

Ответы.

1,2.Указатели.

Указатель —спец.переменные, которые содержат адрес других переменных.

Форма объявления указателя:

Поле модификатор определяет класс памяти или модель памяти указателя или особые преобразования указателя компилятором и является необязательным. Поле тип является обязательным и определяет тип объекта, на который указывает указатель. Возможны следующие типы: базовые (в том числе void), перечислимые, структуры, объединения, другие указатели.

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

Поле список_указателей обязательно и содержит один или несколько идентификаторов указателей, разделенных запятыми. При объявлении указателя компилятор выделяет место в памяти компьютера, необходимое для размещения адреса объекта данного типа (размер адреса не равен размеру объекта). Форма объявления указателя в поле список_указателей:

Поле модификатор содержит модификатор, определяющий класс или модель памяти. Для модели памяти модификатор может принимать значения near, far, huge. Эти ключевые слова используются только в среде программирования Borland C++ 3.1.

Символ * — признак указателя. При объявлении нескольких указателей * ставится перед каждым указателем. Инициализатор — адрес, который инициализирует указатель. Рекомендуется использовать в качестве инициализаторов адреса переменных, константы или инициализированные указатели.

В данном примере объявляются указатели p и f и переменная а.

С указателями связаны две специфические унарные операции: взятие адреса и обращение по адресу. Форма записи операции обращения по адресу: *операнд. Операция производит обращение к объекту, адрес которого храниться в указателе. Операнд — указатель. Форма записи операции взятия адреса: &операнд. Операнд — скалярный объект. Операция возвращает адрес скалярного объекта.

Знаки данных операций имеют аналоги среди бинарных операций и в контексте программы они различаются по количеству операндов, участвующих в операциях.

short int i=10, j=3, k;


124 134

Как и обычные переменные, указатели инициализируются нулевым значением (константа NULL определена в файле stdio.h) при компиляции только если они объявлены на внешнем уровне или с классом памяти static. Для остальных указателей инициализация не проводится. Они указывают на произвольную область памяти, поэтому, прежде чем использовать указатель, его значение необходимо явно определить. Ни в коем случае нельзя присваивать значение указателю непосредственно.

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

Операции над указателями.

Над указателями можно производить операции сложения и вычитания указателей с целыми числами, присвоение и сравнение указателя, кроме того возможна операция вычитания указателей одного типа (складывать указатели нельзя!). Важное значение имеет тип указателя, так как указатель указывает на объект какого-то типа, то есть значения указателя могут изменяться исходя из значения размера переменной данного типа.

printf (“p 64 К /размер типа элементам.

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

Модель TINY.

Общий объем памяти для кода, данных и стека 64 К. Все указатели — ближние.

Модель SMALL.

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

Модель MEDIUM.

Общий объем памяти для каждого модуля — 64 К. Для данных и стека тоже 64 К. Указатели данных по умолчанию ближние. Указатели функций по умолчанию дальние

Модель COMPACT.

Рекомендуется в случае с малым объемом кода, но большим объемом данных. Общий объем памяти для кода 64 К. Для данных — 64 К, для стека — 64 К. Указатели данных по умолчанию дальние, указатели функций по умолчанию ближние. Начиная с этой модели отсутствует ближняя куча

Модель LARGE.

Общий объем памяти для каждого модуля 64 К, для данных — 64 К, для стека — 64 К. Все указатели дальние.

Модель HUGE

Общий объем памяти для каждого модуля64 К. Для данных каждого модуля 64 К, для стека — 64 К. Все указатели дальние.

Динамическое распределение памяти.

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

1. Получение динамической памяти заданного объема.

2. Работа с данными, распределяемыми в динамической памяти (если необходими, то возможно перераспределение выделенного объема памяти с сохранением находящихся в нем данных).

3. Освобождение динамической памяти по окончании работы с данными.

Для получения блока динамической памяти используются функции malloc и calloc. Форма записи:

void *malloc(size_t size);

void *calloc(size_t nitems, size_t size);

Функция malloc в качестве аргумента принимает размер запрашиваемого блока в байтах.

Функция calloc в качестве первого аргумента принимает число элементов, под которые необходимо выделить память. В качестве второго аргумента — размер одного элемента в байтах.

Тип size_t аналогичен типу unsigned int. Функции в случае успешного выделения возвращают указатель на тип void, содержащий адрес выделенного блока, который нужно явно преобразовать к указателю на необходимый тип данных. В случае ошибки (как правило, связанной с тем, что такого объема свободной памяти нет) функции возвращают значение NULL. NULL — стандартная константа языка С, которая обозначает нулевой указатель. После выполнения динамического распределения памяти необходимо обязательно проверять, какое значение возвратили функции. Если значение указателя NULL, то работа с памятью невозможна (в большинстве случаев следует завершить программу).

Для перераспределения уже выделенного объема памяти используется функция realloc. Форма записи:

void *realloc(void *block, size_t size);

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

Для освобождения блока динамической памяти используется функция free. Форма записи:

void *free(void *block);

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

В среде Borland C++ 3.1 для работы с дальней кучей необходимо использовать дальние указатели и соответствующие функции с приставкой far.

5. Функции.

Функция — совокупность объявлений и операторов, предназначенных для выполнения отдельной задачи и заключенных в специальный блок. Необходимость в использовании функции возникает при решении сложных задач, когда нужно выполнять набор однотипных функций с различными данными. Любая программа на языке С должна содержать хотя бы одну функцию (main). С использованием функций в языке С связано три основных понятия: объявление, определение и вызов.

Определение функции — описание действий, выполняемых функцией. Форма записи:

модификатор 1 тип модификатор 2 имя (список_формальныхпараметров)

Поле модификатор 1 содержит спецификации класса памяти. Поле модификатор 2 (модификатор стека) используется только в средах разработки фирмы Borland для изменения типа функции (cdecl или pascal). Подробнее — при рассмотрении классов памяти.

Поле тип определяет тип возвращаемого функцией в вызывающую функцию значения. Если тип не указан явно, то по умолчанию считается, что функция возвращает значение типа int. Если функция не возвращает значения, она должна иметь тип void. Функция может возвращать любой скалярный объект или указатель, но не может возвращать массив или другую функцию. Для этого используются указатели.

Поле имя определяет идентификатор функции.

Поле список_формальных_параметров содержит объявление параметров функции, указанных через запятую, которые могут быть использованы в теле функции. Если функция не имеет параметров, то в данном поле указывается слово void. Формальные параметры — локальные переменные, существующие только в пределах тела функции и принимающие значения, переданные функции при вызове в соответствии с порядком следования их имен в списке параметров. Синтаксические правила объявления формальных параметров функции аналогичны синтаксическим правилам объявления переменных. Тип формальных параметров может быть любым, но типы формальных параметров должны соответствовать типам формальных параметров, указанных в объявлении функции и типам фактических параметров, передаваемых в тело функции при ее вызове.

Поле тело функции задает последовательность операторов, выполняемых функцией.

Объявление функции (задание прототипа функции) — задание формы обращения к функции. Форма записи:


модификатор 1 тип модификатор 2 имя (список_формальных_параметров);

Смысл полей модификатор 1 и модификатор 2, тип и имя такой же, как и при определении функции.

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

Вызов функции — передача управления на первый оператор тела функции:

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

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

Вызов функции приводит к следующим действиям:

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

2. В стек копируется адрес возврата из функции (адрес следующего за вызовом функции команды).

3. Значения фактических параметров копируются в ячейки памяти, предназначенные для хранения аргументов функции (в стек).

4. Управление передается по адресу функции, который является первым оператором тела функции.

5. Последовательно выполняются операторы, составляющие тело функции. Выполнение оператора return в теле функции возвращает управление в вызывающую функцию. При отсутствии оператора return управление возвращается после выполнения последнего оператора тела функции.

6. При возврате из функции производится очистка стека: удаляются фактические параметры и адрес возврата, по которому передается управление в вызывающую функцию. Если функция возвращает значение оператором return, то производится возврат значения в точку вызова.

7. Управление передается на следующую за вызовом функции команду.

Параметры функции.

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

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

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

LocalStorage на пальцах

Один из наших читателей прислал статью с рассказом о HTML5 LocalStorage в браузерах. Передаём ему слово.

Я постарался написать самое простое и понятное руководство по использованию технологии localStorage. Статья получилась совсем небольшой, в силу того, что и сама технология и средства работы с ней не несут ничего сложного. Для старта вам достаточно чуть-чуть знать JavaScript. Итак, уделите этой статье 10 минут и вы смело сможете добавить себе в резюме строчку «умею работать с localStorage».

Что такое localStorage?

Так выглядит JavaScript объект:

А так выглядит JSON. Почти так же как обычный js-объект, только все свойства должны быть заключены в кавычки.

Чтобы понять, что такое localStorage, просто представьте, что где-то у вас в браузере встроен такой объект, которым мы можем пользоваться. При этом данный объект не очищает значения, которые мы туда запишем, если мы перезагрузим страницу или даже совсем закроем браузер.

Если говорить языком JavaScript, то localStorage это свойство глобального объекта браузера (window). К нему можно обращаться как window.localStorage или просто localStorage.

Еще стоит сказать, что у браузера существует клон localStorage, который называется sessionStorage. Их разница только в том, что последний хранит данные только для одной вкладки (сессии) и просто очистит свое пространство как только мы закроем вкладку

12–13 ноября, Санкт-Петербург, беcплатно

Давайте посмотрим на него вживую. Например, в Google Chrome вам надо открыть DevTools (F12), перейти на вкладку «Resourses» и на левой панели вы увидите localStorage для данного домена и все значения, что оно содержит.

Кстати, вы должны знать как localStorage работает с доменами. Для каждого домена ваш браузер создает свой объект localStorage, и редактировать или просматривать его можно только на этом домене. Например, с домена mydomain-1.com нельзя получить доступ к localStorage вашего mydomain-2.com .

Зачем мне нужен localStorage?

LocalStorage нужен только для одного — хранить определенные данные между сессиями пользователя. Можно придумать тысячу и одну вещь, которую можно хранить в локальном хранилище браузера. Например, браузерные игры, которые используют его как сохраненку, или записать момент, на котором пользователь остановился при просмотре видео, различные данные для форм и т.д.

Как мне начать работать с localStorage?

Работа с localStorage очень напоминает работу с объектами в JavaScript. Существует несколько методов для работы с ним.

Метод который добавляет в localStorage новый ключ со значением (а если такой ключ уже существует, то перезаписывает новым значением). Пишем, например, localStorage.setItem(‘myKey’, ‘myValue’);

Берем определенное значение из хранилища по ключу.

Очищаем все хранилище

Сейчас вы можете открыть вкладку с localStorage вашего браузера и самостоятельно потренироваться записывать и извлекать данные из этого хранилища. Если что – весь код пишем в js-файл.

Также хочется отметить, что localStorage отлично работает и с вложенными структурами, например, объектами.

Вы также должны знать, что браузеры выделяют 5мб под localStorage. И если вы его превысите — получите исключение QUOTA_EXCEEDED_ERR. Кстати, c его помощью можно проверять есть ли в вашем хранилище еще место.

Вместо заключения

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

Пробел или в регулярном выражении

У меня есть строка:

Мне нужно получить

Какова причина того, что символы после пробела обрезаются?

Примечание: символов не всегда равное количество, бывает, что пробела в строке нет.


1 ответ 1

Две звезды необязательны, и, строго говоря, вы можете получить первым совпадением один символ. Или ноль символов.

Вот этот кусок имеет право совпасть ни с чем, что он, видимо и делает. Подозреваю, что если инвертировать жадность, то все взлетит. Но что точно можно было бы сделать:

  • Регулярное выражение должно заканчиваться на начале следюущего тега, а не там, где оно захочет (как здесь)
  • У нас должен быть как минимум один символ совпадения, а значит: (\w*)(?:\s| )*(\w+)
  • Нам вообще не нужны три последовательные подмаски: ([\w]+(?:(?:\s| )\w+)*)
  • В конце концов, нас интересует, все содержимое тега: \>([^\

Теперь о подходе вообще.

Забудьте о парсинге HTML с помощью регулярок. Завтра у вас с этим совпадет

Компьютерные технологии (архив 2012г.)

9 Лекция. Массивы и указатели. Динамическая память (HEAP — КУЧА). Глобальные, статические переменные и константы.

Массивы и указатели.

Пример: Массивы и указатели

В некоторых случаях алгоритм на указателях будет работать быстрее.

Пример: Быстродействие без указателей

Пример: Быстродействие на указателях

Глобальные, статические переменные и константы

Глобальные переменные — объявляются вне функции, память выделяется при создании процесса в сегменте данных (data).

  • при использовании могут пересекаться имена переменных с локальными
  • хранятся в сегменте данных (data)
  • глобальная видимость
  • глобальное время жизни

Статические переменные — объявляются как локальные или глобальные, память выделяется при создании процесса в сегменте данных (data).

  • глобальное время жизни
  • хранятся в сегменте данных (data)
  • локальная видимость

Статическая переменная имеет глобальное время жизни и одновременно локальную область видимости – она доступна только внутри блока, в котором объявлена.

Константа — значение переменной изменить нельзя, могут быть локальными, глобальными, статическими и т.д.

Пример: Объявление глобальной переменной и константы

Использование кучи (Heap) (Динамическая память)

Рис. Распределение виртуальной памяти процесса.

Создание буфера в куче для 10 элементов по 4 байта

int *p = malloc(10*4); // размер задается в байтах

free(p); // освобождения памяти (автоматически память не освобождается)

int *p = malloc(10*4);

int *p = malloc(10*sizeof(int));

char *p = malloc(10*1);

char *p = malloc(10*sizeof(char));

Пример: выделение и освобождение памяти в куче

Функция malloc и free

void *malloc(size_t size); // функция выделения памяти

void free(void* memblock); // функция освобождения памяти

size_t — размер выделяемой области памяти (в куче) в байтах

typedef unsigned int size_t — задать новый тип данных size_t

unsigned — без знаковый тип

Другие функции для работы с кучей

calloc (от англ. clear allocation, выделение чистой (обнуленной) памяти)

realloc (от англ. reallocation, перераспределение (расширение) памяти)

Утечка памяти

Два вызова, без освобождения, на тот же указатель:

Int *p = malloc(10*sizeof(int)); // выделен блок

p = malloc(100*sizeof(int)); // выделен блок


К чему это приводит:

  • замедление работы (начинает использовать swap)
  • фрагментация
  • аварийная остановка

Переполнение кучи на языке Си

Пример: Переполнение кучи на языке Си

Переполнение буфера кучи на языке Си

Пример:Переполнение буфера кучи на языке Си

Обобщения и дополнения

Терминология — «Динамический массив» — под этим часто подразумевают следующее 3 понятия:

  • Variable-length array – переменный (динамический) размер массива в стеке
    int m[n]; //добавлено в C99 и C11, ранее ф-я alloca() или в куче
  • Array on heap (dynamically allocated array) – массив в кучи (динамической памяти)
  • Dynamic array (growable, resizable) – автоматически увеличивается по мере наполнения.

«Настоящие» динамические массивы это последнее (Dynamic array).

Применение указателей

  1. Передача значение из функций (например swap)
  2. Передача массивов
  3. Быстродействие
  4. Работа в Heap (Куче)

Память

  1. Глобальная память (быстрая)
  2. Стек (достаточно быстрая)
  3. Динамическая память (выделение медленное, ОС ищет непрерывный «кусок»)

Лабораторная работа: Динамическое распределение памяти

ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ

Введение

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

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

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

Студенты должны научиться исследованию программ, используя режим отладки. Они должны убедиться в пользе отладчика не только при поиске ошибок, но и на стадии разработки программы.

Для обязательного выполнения рекомендуются задания 1, 6, 7, 10. Задание 4 можно использовать в качестве курсовой работы.

1. Модели памяти

Виды моделей

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

В BC++2.0 существуют несколько видов моделей памяти: tiny, small, medium, compact, large, huge. Программа может быть откомпилирована в любой из этих моделей, если установить соответствующий флажок в Opions-Compiler-Code Generation-Model. При этом используются различные наборы встроенных библиотек и создаются различные obj-файлы. Например, для модели large в директории Lib имеются файлы C0l.obj, Cl.lib, Mathl.lib и другие. Возможная ошибка на этапе линковки (“Не могу найти файл Сol.obj”) вызвана неполной версией пакета и неверным выбором модели.

Модели памяти не являются частью языка Си, а зависят от используемой оболочки.

Когда программа загружается в ОЗУ для выполнения, ей отводится определенное место, в котором можно выделить несколько областей: область кода (ОК); область данных, глобальных и статических переменных(ОД); стек; куча (heap или динамическая память).

Название: Динамическое распределение памяти
Раздел: Рефераты по информатике, программированию
Тип: лабораторная работа Добавлен 21:07:12 06 июля 2009 Похожие работы
Просмотров: 231 Комментариев: 14 Оценило: 3 человек Средний балл: 5 Оценка: неизвестно Скачать
вид модели памяти максимальный размер
Область кода область данных стек куча взаимное расположение
tiny 64К 64К 64К 64К все области в одном сегменте
small 64К 64К 64К 64К ОД,стек и куча в одном сегменте
medium 64К 64К 64К различные сегменты
compact 64К 64К различные сегменты
large 64К 64К различные сегменты
huge 64К 64К различные сегменты

Модель Large

Модели памяти устроены по-разному. Рассмотрим расположение областей памяти в модели large.

PSP представляет собой префикс программного сегмента, занимает 256 байт и помещается перед исполняемыми файлами при их загрузке в память. Он содержит переменные, используемые MS-DOS для управления программой, а также место для переноса данных окружения.

Область кода содержит машинные коды функций программы. Функции, присоединенные к exe-файлу на стадии линковки, размещаются вне области кода.

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

В стеке размещаются локальные переменные, параметры, передаваемые функциям, и ряд других данных. Как правило, стек растет сверху вниз, занимая пульсирующую непрерывную область. В случае переполнения стека происходит его «налезание» стека на область данных и выдается соответствующее сообщение. Проверка стека увеличивает время работы программы и ее можно отключить в Options-Entry/Exit Code Generation-Stack options-Test stack overflow.

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

Сегментные регистры

Сегмент — это область памяти размером не более 64K, созданная для хранения кода, данных или стека. Сегменты всегда выровнены на границу параграфа (16 байт), поэтому их адрес получается умножением сегментного регистра на 0x10=16.

В некоторых моделях памяти код может занимать больше 64K. Области кода, данных и стек начинаются с параграфов с номерами содержащимися, соответственно, в регистрах CS, DS, SS.

В режиме отладчика можно просматривать содержимое регистров в окне Window-Register. При повторных запусках программы сегментные регистры могут быть различны.

Значения сегментных регистров, кроме того, хранятся в глобальных псевпеременных, имеющих то же название с подчеркиванием: _CS, _DS и т.д. Их можно использовать в программе, например, распечатать, учитывая, что псевпеременные являются шестнадцатиричными беззнаковыми числами printf(”CS =%x”, _CS);

Просмотр переменных


В режиме отладчика можно определить адреса и значения переменных, находящихся в своей области видимости, с помощью Alt-F4. Например.

— Для переменной i, описанной как inti; i = 5; окно просмотра имеет вид

8FAC:FFF4 5(0x0005)
Int

Здесь указан адрес переменной в формате [сегмент]:[смещение]. Полный адрес переменной равен 8FAC0+FFF4=9FAB4

— Для переменной ptri, описанной как

окно просмотра имеет вид

8FAC:FFF2 п Ds:FFF4
[0] 4(0x0004)
Int *

Здесь указаны: адрес самой переменной ptri, равный 8FAC:FFF2; значение этой переменной Ds:FFF4; а также содержимое ячейки по адресу Ds:FFF4, т.е. значение i. Для того, чтобы узнать содержимое ячеек, окружающих переменную i, нужно воспользоваться комбинацией клавиш Alt-I, ввести начальный индекс (Starting index) и число ячеек (Count). Если, например, введены числа -5 и 15, то можно в приведенном выше окне можно просмотреть элементы массива ptri[-5], ptri[-4],…,ptri[10].

Объекты, размещенные в куче, не имеют имени. Поэтому просматривать динамические объекты можно только предыдущим способом, т.е. по адресу.

Абсолютные адреса и размеры областей памяти в модели large

Область кода занимает часть ОЗУ, содержащую параграфы с номерами от CS до DS-1. Таким образом, размер области кода равен (DS-CS)*0x10.

Область данных занимает часть ОЗУ, содержащую параграфы с номерами от DS SS-1. Таким образом, размер области данных равен (SS-DS)*0x10.

Стек начинается с параграфа SS, но растет справа налево, от старших адресов к младшим. Новые данные помещаются в стек в вершине стека. Смещение вершины стека относительно SS равно SP. Таким образом, полный адрес вершины равен SS:SP. В начале программы стек пуст, поэтому он занимает ячейки с адресами от SS*0x10 до SS*0x10, а размер стека равен SP.

На кучу остается память с адреса SS*0x10+SP по 0xA0000=640K.

Реальный размер кучи зависит от способа запуска программы. Сама оболочка Borland C++2.0 занимает в ОЗУ порядка 200K, и для программы, выполнямой из оболочки, остается немного места — примерно 300K. Если же программа запускается из командной строки DOS или из Norton Commander, то куча будет значительно больше.

При выполнении программы из оболочки и в отладчике под кучу отводится еще меньше места, поэтому ее размер в этом случае разрешается устанавливать вручную с помощью опции Options-Debugger-Program heap size.

2. Основные функции ДРП

Выделение памяти

void *malloc(size_t size);

Выделяет блок ДП размером size байт. Здесь size_t совпадает с типом unsignedint. Таким образом, блок не превосходит размер в 64K. В случае отсутствия непрерывного блока заказанной длины, возвращается NULL.

Пример 1. Выделение массива для 1000 чисел типа float.

if((ptrf =(float *) malloc(1000 * sizeof(float)) == NULL)

printf(«\nОшибка при выделении памяти!»);

Освобождение памяти

void free(void *block);

Освобождает блок ДП, начинающийся с адреса block. Этот адрес должен находится в заголовке ранее выделенного блока (см. п.3). В противном случае, будет освобожден случайный блок и возникнет логическая ошибка. Динамические переменные не освобождаются автоматически при выходе из области действия и, таким образом, засоряют кучу.

Перевыделение памяти

void *realloc(void *block, size_t size);

Изменяет размер блока, на который указывает block. Новый размер блока будет равен size. Старая информация сохраняется. При нехватке свободных байт блок будет перемещен в новое место с одновременным копированием информации. Функция возвращает адрес нового блока, не изменяя переменную block.

Пример 2. Выделение памяти под двумерный массив

A = (float **)malloc(m * sizeof(float *));

printf(«\nОшибка при выделении памяти под массив указателей!»);

Управление памятью: Взгляд изнутри

Доброго времени суток!
Хочу представить вашему вниманию перевод статьи Джонатана Барлетта (Jonathan Bartlett), который является техническим директором в компании New Medio. Статья была опубликована 16 ноября 2004 года на сайте ibm.com и посвящена методам управления памятью. Хотя возраст статьи достаточно высок (по меркам IT), информация в ней является фундаментальной и описывает подходы к распределению памяти, их сильные и слабые стороны. Всё это сопровождается «самопальными» реализациями, для лучшего усвоения материала.

Аннотация от автора
Решения, компромиссы и реализации динамического распределения памяти
Получите представление о методах управления памятью, которые доступны Linux разработчикам. Данные методы не ограничиваются языком C, они также применяются и в других языках программирования. Эта статья даёт подробное описание как происходит управление памятью, на примерах ручного подхода (manually), полуавтоматического (semi-manually) с использованием подсчёта ссылок (referencing count) или пула (pooling) и автоматического при помощи сборщика мусора (garbage collection).

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

Было время, когда управления памятью не было большой проблемой. В качестве примера можно вспомнить времена разработки на ассемблере под Apple II. В основном программы запускались не отдельно от ОС, а вместе с ней. Любой участок памяти мог использоваться как системой, так и разработчиком. Не было необходимости в расчёте общего объёма памяти, т.к. она была одинакова для всех компьютеров. Так что требования к памяти были достаточно статичны — необходимо было просто выбрать участок памяти и использовать его.

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

  • Определить, имеется-ли достаточный объём памяти;
  • Получить секцию из доступной памяти;
  • Вернуть секцию обратно в пул, чтобы её можно было использовать в других частях программы или другими программами.

(прим. переводчика: Обозначим данный список как Memory-Requirements, чтобы ссылаться на него в дальнейшем)
Библиотеки, которые занимаются поиском/выделением/освобождением памяти называются allocator-ми. С ростом сложности программы, повышается сложность управления памятью и тем самым повышается роль allocator-а в такой программе. Давайте взглянем на различные метод управления памятью, рассмотрим их преимущества и недостатки, а также ситуации, где они наиболее эффективны.

Аллокаторы (C-Style)
Язык C поддерживает две функции, которые занимаются решением задач из Memory-Requirements:

  • malloc: Выделяет заданное число байт и возвращает указатель на них. Если памяти недостаточно, возвращает указатель на NULL (null pointer);
  • free: Принимает на вход указатель на область в памяти, выделенной с помощью malloc и возвращает её для дальнейшего использования в программе или операционной системе (на самом деле, некоторые malloc возвращают память для последующего использования только программе, но не ОС).

Физическая и виртуальная память
Для понимая, как происходит выделение в пределах программы, необходимо иметь представление как ОС выделяет память под программу. (прим. переводчика: т.к. программа запускается под конкретной ОС, то именно она решает, сколько памяти выделить под ту или иную программу) Каждый запущенный процесс считает что имеет доступ ко всей физической памяти компьютера. Очевиден тот факт, что одновременно работает множество процессов, и каждый из них не может иметь доступ ко всей памяти. Но что будет если процессам использовать виртуальную память (virtual memory).

В качестве примера, допустим программа обращается к 629-у участку в памяти. Система виртуальной памяти (virtual memory system), не гарантирует что данные хранятся в RAM по адресу 629. Фактически, это может быть даже не RAM — данные могли быть перенесены на диск, если RAM оказалась вся занята. Т.е. в вирт. памяти могут храниться адреса, соответствующие физическому устройству. ОС хранит таблицу соответствий вирт. адресов к физическим (virtual address-to-physical address), чтобы компьютер мог правильно реагировать на запрос по адресу (address requests). Если RAM хранит физические адреса, то ОС будет вынуждена временно приостановить процесс, выгрузить часть данных НА ДИСК (из RAM), подгрузить необходимые данные для работы процесса С ДИСКА и перезапустить процесс. Таким образом, каждый процесс получает своё адресное пространство с которым может оперировать и может получить ещё больше памяти, чем ему было выделила ОС.

В 32-х битных приложениях (архитектура x86), каждый процесс может работать с 4 гигабайтами памяти. На данный момент большинство пользователей не владеют таким объёмом. Даже если используется подкачка (swap), всё равно должно получиться меньше 4 Гб на процесс. Таким образом, когда процесс выгружается в память, ему выделяется определённое пространство. Конец этого участка памяти именуется как system break. За этой границей находится неразмеченная память, т.е. без проекции на диск или RAM. Поэтому когда у процесса заканчивается память (из той, что ему была выделена при загрузке) он должен запросить у ОС больший кусок памяти. (Mapping (от англ. mapping — отражение, проекция ) — это математический термин, означающий соответствие один к одному — т.е. когда по виртуальному адресу хранится другой адрес (адрес на диске), по которому уже хранятся реальные данные)

ОС на базе UNIX имеют в своём арсенале два системных вызова для дополнительной разметки памяти:

  • brk:brk() — это очень простой системный вызов. System break — это крайняя граница размеченной для процесса памяти. brk() просто перемещает эту границу вперёд/назад, чтобы увеличить или уменьшить объём выделенной памяти. (прим. переводчика: представьте шкалу масштаба в том же MS Word. System break — это макс. значение, которое может принять бегунок, а сам бегунок — Current break);
  • mmap:mmap() (или “memory map”) аналогичен brk(), но является более гибким инструментом. Во-первых, он может разметить память в любом месте адресного пространства, а не только в конце процесса. Во-вторых, он может не просто разметить память (виртуальную) как проекцию к физической или свопу (swap), он может привязать память к конкретным файлам так, что чтение и запись будут оперировать непосредственно с файлом. Антиподом mmap() является munmap().

Как вы можете видеть, простые вызовы brk() или mmap() могут быть использованы для расширения памяти процесса. Дальше по тексту будут использоваться brk() т.к. он является наиболее простым и распространённым инструментом.


Реализация простого allocator-а
Если вы писали программы на языке C, то наверняка использовали такие функции как malloc() и free(). Наверняка вы даже не задумывались о их реализации. Этот раздел продемонстрирует упрощённую реализацию этих функций и проиллюстрирует как они участвуют в распределении памяти.
Для примера нам понадобится вот этот листинг. Скопируйте и вставьте его в файл под названием malloc.c. Его мы разберём чуть позже.
Распределите памяти в большинстве операционных систем повязана на двух простых функциях:

  • void* malloc(long numbytes): Выделяет в памяти numbytes байт и возвращает указатель на первый из них;
  • void free(void* firstbyte): firstbyte — указатель полученный с помощью malloc() и память по которому необходимо освободить.

Объявим функцию malloc_init, которая будет инициализировать наш allocator. Это подразумевает три вещи: Помечать allocator как проинициализированный, находить в памяти последний валидный адрес (т.е который можно было бы использовать для выделения) и установка указателя на начало этой памяти. Для этого объявим три глобальные переменные:

Листинг 1: Глобальные переменные для нашего аллокатора

Как упоминалось выше, «край» размеченной памяти (последний действительный адрес) имеет несколько названий — System break или Current break. В большинстве Unix-like систем, для поиска current system break, используется функция sbrk(0). sbrk отодвигает current break на n байт (передаётся в аргументе), после чего current break примет новое значение. Вызов sbrk(0) просто вернёт current system. Напишем код для нашего malloc, который будет искать current break и инициализировать переменные:
Листинг 2: Инициализации Allocator-а

Для правильного управления, необходимо следить за выделяемой и освобождаемой памятью. Необходимо помечать память как “неиспользуемую”, после вызова free() для какого либо участка памяти. Это необходимо для поиска свободной памяти, когда вызывается malloc(). Таким образом, начало каждого участка памяти, которое возвращает malloc() будет будет иметь следующую структуру:
Листинг 3: Структура Memory Control Block

Можно догадаться, что данная структура будет мешать, если вернуть на неё указатель (вызов функции malloc). (прим. переводчика: имеется ввиду, что если указатель установить на начало этой структуры, то при записи в эту память, мы потеряем информацию о том, сколько памяти было выделено) Решается всё достаточно просто — её необходимо скрыть, а именно вернуть указатель на память, которая располагается сразу за этой структурой. Т.е. по факту вернуть указатель на ту область, которая не хранит в себе никакой информации и куда можно “писать” свои данные. Когда происходит вызов free(), в котором передаётся указатель, мы просто отматываем назад некоторое количество байт (а конкретно sizeof(mem_control_block) ), чтобы использовать данные в этой структуре для поиска в дальнейшем.

Для начала поговорим об освобождении памяти, т.к. этот процесс проще чем выделение. Всё что необходимо сделать для освобождения памяти, это взять указатель, переданный в качестве параметра функции free(), переместить его на sizeof(struct mem_control_block) байт назад, и пометить память как свободную. Вот код:
Листинг 4: Освобождение памяти

Как вы можете заметить, в данном примере освобождение происходит за константное время, т.к. имеет очень простую реализацию. С выделением уже немного сложнее. Рассмотрим алгоритм в общих чертах:
Листинг 5: Псевдокод алгоритма работы аллокатора

Вся суть заключается в своего рода “прогулке” по памяти с целью нахождения свободных участков. Взглянем на код:
Листинг 6: Реализация алгоритма работы

Это наш Memory Manager. Теперь его необходимо собрать, для использования в своих программах.
Чтобы построить наш malloc-подобный allocator, нужно набрать следующую команду (мы не затронули такие функции как realloc(), но malloc() и free() являются наиболее значимыми):
Листинг 7: Компиляция

На выходе получим файл malloc.so, который является библиотекой и содержит наш код.
На Unix системах, вы можете использовать свой allocator, вместо системного. Делается это так:
Листинг 8: Заменяем стандартный malloc

LD_PRELOAD это переменная среды окружения (environment variable). Она используется динамическим линковщиком (dynamic linker) для определения символов, которые содержаться в библиотеке, перед тем как эта библиотека будет подгружена каким-либо приложением. Это подчёркивает важность символов в динамических библиотеках. Таким образом, приложения, которые будут создаваться в рамках текущей сессии, будут использовать malloc(), которой мы только что написали. Некоторые приложения не используют malloc(), но это скорее исключение, чем правило. Другие же, которые использую аллокаторы на подобии realloc(), или которые не имеют представления о внутреннем поведении malloc(), скорее всего упадут. Ash shell (ash — это командная оболочка UNIX подобных систем) отлично работает с нашим malloc аллокатором.

Если вы хотите убедиться в том, что используется именно ваш malloc(), можете добавить вызов write() в начало ваших функций.

В плане функционала, наш менеджер памяти (Memory manager) оставляет желать лучшего, но он отлично подходит в качестве примера для демонстрации работы. Из его недостатков следует отметить:

  • Т.к. он работает с System break (глобальная переменная), он не может сосуществовать с другими аллокаторами или с mmap;
  • При распределении, аллокатору, в худшем случае придётся пройти через всю память процесса, которая между прочим также может включать в себя адреса данных, которые хранятся на диске. Это приведёт к тому, что ОС будет тратить время на перемещение данных с диска в вирт. память и обратно;
  • Он обладает не самой лучше обработкой ошибок, связанных с недостатком памяти (out-of-memory);
  • Не имеет реализации множества других функций, таких как realloc();
  • Т.к. sbrk() может выделить больше памяти, чем мы запросили, это повлечёт утечку памяти в конце кучи;
  • is_available использует 4 байт, хотя по факту, необходим всего один бит;
  • Аллокатор не обладает потоковой безопасностью (thread-safety);
    Не может сливаться в более крупные блоки. (прим. переводчика: допустим мы запрашиваем 32 байта. В памяти есть следующие друг за другом два свободных блока по 16 байт. Аллокатор это не учтёт.);
  • Использует нетривиальный алгоритм, который потенциально ведёт к фрагментации памяти;
  • Конечно есть и другие проблемы. Но ведь это только пример!

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

  • Скорость выделения памяти (allocation speed);
  • Скорость освобождения (deallocation speed);
  • Поведение в многопоточной среде;
  • Поведение при кончающейся памяти;
  • Размещение кэша;
  • Учёт дополнительных расходов на память;
  • Поведение в виртуальной памяти;
  • Большие и маленькие объекты;
  • Стабильная работа в режиме реального времени.

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

Существует много разновидностей аллокаторов. Вот некоторые из них:

  • Doug Lea malloc: является целым подмножеством аллокаторов, включающее в себя оригинальные Doug Lea аллокаторы, GNU libc аллокаторы и ptmalloc. Deug Lea аллокаторы имеют похожую структуру что и наш аллокатор, но имеет в своём арсенале индексы для более быстрого поиска и может объединять несколько неиспользуемых блоков в один большой. Также имеется поддержка кэширования, которое ускоряет процесс повторного использования недавно освобождённой памяти. ptmalloc — тот же Deug Lea, который был расширен для поддержки многопоточности. Описание Doug Lea’s malloc доступно в списке литературы в конце статьи.
  • BSD malloc: BSD Malloc, реализация, которая распространяется в BSD начиная с версии 4.2 и включена в FreeBSD в качестве аллокатора, который размещает в памяти объекты из пула, с заранее известным размером. Он имеет в своём распоряжении размер классов относящихся к объектам — степень двойки минус константа. Так что если вы запросите память под объект, то он просто выделит память любого из классов, размер которого будет подходящим. Это обеспечивает простую реализацию, но возможны издержки памяти. Описание также доступно в конце статьи.
  • Hoard: Hoard был написан с целью быстрой работы в многопоточной среде. Поэтому он заточен под работу с блокировками, которые помогают работать с процессами, ожидающими выделения памяти Это может существенно ускорить многопоточные процессы, которые постоянно работаю с памятью. Описание в списке литературы.

Это наиболее известные из множества аллокаторов. Если ваше приложение нуждается в особом распределении памяти, то вы можете написать самопальный (кастомный — custom) аллокатор, который будет работать исходя из поставленных требований. Как бы то ни было, если вы не знакомы с концепцией работы аллокатора, то самописные реализации создадут больше головной боли, чем принесут профита. Для более глубокого введения в предметную область, советую ознакомиться со следующей книгой: Дональд Кнут: Искусство программирования Том 1: Основные алгоритмы — раздел 2.5: Динамическое выделение памяти. Конечно материал устаревший, там не затронута работа с вирт. памятью окружения, но база у алгоритмов практически не изменилась.

В C++ вы можете реализовать свой аллокатор для класса или шаблона с помощью перегрузки (overload) оператора new(). Андрей Александреску в своей книге Современное программирование на C++ описал небольшой объект аллокатора (Глава 4: Размещение в памяти небольших объектов).

Недостатки распределения с помощью malloc()
Не только наш менеджер памяти имеет недостатки, они также присутствуют и у других реализаций. Управление с помощью malloc() довольно опасная вещь для программ, которые хранят данные долгое время и которые должны быть легко доступны. Имея множество ссылок на динамически выделенную память, часто бывает затруднительно узнать, когда её необходимо освободить. Менеджер, обычно легко справляется со своей работой, если время жизни переменной (или время работы с куском памяти), ограничено рамками какой-либо функции (локальные переменные), но для глобальных переменных, чья память используется на всём протяжении работы программы, задача становится значительно сложнее. Также многие API описаны не совсем чётко и становится не понятно, на ком лежит ответственность за управление памятью — на самой программе или на вызванной функции.
Из-за подобных проблем, многие программы работают с памятью согласно собственным правилам. Иногда может показать, что больше операций (прим. переводчика: по тексту “больше кода”) тратится на выделение и освобождение памяти, чем на вычислительную составляющую. Поэтому рассмотрим альтернативные способы управления памятью.

Полу-автоматические (semi-automatic) подходы к управлению памятью

Подсчёт ссылок (reference-counting)
Подсчёт ссылок (reference-counting) — это полу-автоматический метод работы с памятью, требующий дополнительного кода и при котором можно не следить за тем, когда память перестаёт использоваться. Reference-counting делает это за вас.

Механизм работы следующий — для каждой динамически выделенной памяти существует поле, которое хранит число ссылающихся на неё ссылок. Если в программе появляется переменная, ссылающаяся на этот кусок памяти, счётчик увеличивается. И наоборот — при уменьшении переменных, ссылающихся на эту память, счётчик уменьшается. При декременте счётчика, происходит проверка — если количество ссылок 0, то память освобождается.

Каждая ссылка ссылающаяся на эту память, просто увеличивает или уменьшает счётчик. Это предотвращает ситуации очистки памяти, когда она используется. В любом случае, вы не должны забывать использовать функции отвечающие за подсчёт ссылок, если работаете с таким типом (“подсчитываемых”) структур. Также встроенные функции и сторонние библиотеки могут не уметь работать с reference-counting или иметь свой механизм работы.

Для реализации этого механизма, вам достаточно двух функций. Первая будет увеличивать счётчик ссылок, вторая уменьшать и освобождать память, если он достиг нуля.
Например функция подсчёта ссылок может выглядеть примерно так:
Листинг 9. Принцип работы reference-counting

REF и UNREF могут быть более сложными — всё зависит от того, какие цели вы преследуете. К примеру, вы захотите добавить блокировку для многопоточных приложений. Тогда вам нужно будет добавить в refcountedstruct, указатель на функцию для освобождения памяти (подобно деструктору в объектно-ориентированных языках — это необходимо, если ваша структура содержит указатели)
При использовании REF и UNREF, необходимо придерживаться следующих правил при присваивании указателей:

  • UNREF — вызывается перед присваиванием
  • REF — вызывается после присваивания

Для функций, которые принимают recounted структуры, используются следующие правила:

  • REF — вызывается начале функции
  • UNREF — вызывается в конце функции

Вот ещё один небольшой пример:

Т.к. reference counting достаточно тривиальный механизм, то многие разработчики реализовывают его самостоятельно, избегая сторонних библиотек. Однако их реализации всё равно базируются на аллокаторах подобных malloc и free, которые и занимаются выделением и освобождением памяти. Reference counting находит применение и в языках высокого уровня, например Perl. Данные обязанности возлагаются на сам язык, так что вам не нужно ни о чём беспокоится, если вы конечно не захотите заняться его расширением. Безусловно, подсчёт ссылок незначительно понижает скорость работы, но зато добавляет немного безопасности и простоты в разработку. Рассмотрим основные преимущества:

  • Простая реализация;
  • Просто использовать;
  • Ссылка на объект является частью структуры что обеспечивает хорошую локальность кэша (cache locality).

Так же есть и недостатки:

  • Необходимо помнить о вызове функции подсчёта ссылок;
  • Нельзя освобождать память если объект есть часть кольцевой структуры;
  • Понижение скорости при присваивании указателя;
  • Необходима особая осторожность в процессе обработки исключений (try или setjmp()/longjmp() );
  • Требуется дополнительная память при работе с ссылками;
  • Reference counter находится на первом месте в структуре, что даёт быстрый доступ на большинстве машин;
  • Медленное выполнение и дополнительные сложности при работе в многопоточной среде.

C++ может снизить вероятность ошибки посредством «умных» указателей (smart pointers), которые работают с указателями также кропотливо как и reference counting. Если вы является обладателем legacy кода, который работает не под управлением smart pointers (например, linkage в библиотеке C) то дальнейшее использование этого кода приведёт к страшному беспорядку, а код станет сложным и запутанным по сравнению с кодом, который управляется умными указателями. Поэтому их обычно используют только в C++ проектах. Если вы хотите использовать умные указатели, то вам просто необходимо прочитать главу “Умные указатели” книги Современное программирование на C++ (автор Андрей Александрексу).

Memory pools
Memory pools ещё один способ полу-автоматического управления памятью. Он автоматизирует процесс для программ, которые проходят через определенные стадии/фрагменты (stages) выполнения, на каждой стадии которой известно сколько места потребуется программе. В качестве примера можно привести серверные процессы, где выделено много памяти под соединения — у неё максимальное время жизни (lifespan) совпадает с временем жизни соединения. Тот же Apache — каждое соединение это отдельный stage, который имеет свой memory pool. После выполнения фрагмента, память моментально освобождается.

В “пуловой” модели управления, каждое выделение памяти относится к конкретному пулу, из которого память и будет выделена. (прим. переводчика: представьте функцию, в которой 5 локальных переменных типа char. Т.е. заранее известно что при выполнении этой функции, необходимо будет 5 байт памяти. Т.е. тело этой функции это как stage через который проходит программа в процессе выполнения, и можно сразу под него выделить кусок памяти фиксированного размера в 5 байт. Это сэкономит время на поиск и “резку” памяти по мере появления переменных в функции и даст гарантию того, что памяти всегда хватит.) Каждый pool имеет своё время жизни. В apache, pool может иметь время жизни равное времени работы сервера, длительности соединения, времени обработки запроса и т.д… Поэтому, если имеется набор функций, которые требуют память не превышающую размер соединения, то её можно просто выделить из пула соединений и по завершению работы, она будет освобождена автоматически. Кроме того, некоторые реализации дают возможность регистрировать функции очистки (cleanup functions), которые вызываются чтобы выполнить некоторые действия перед тем как пул будет очищен (что-то вроде деструкторов в ООП).

Чтобы использовать пул в своих программах, вы можете просто воспользоваться реализацией obstack (GNU — libc) или Apache Protable Runtime (Apache). Преимущество obstack это то, что он по умолчанию идёт со всеми Linux дистрибутивами. А Apache Portable Runtime это возможность использования на множестве платформ. Чтобы узнать больше об их реализациях, в конце статьи лежат ссылки.

Следующий “надуманный” пример демонстрирует применение obstack:
Листинг 11. Пример с использованием obstack

Множественные вызовы realloc(), по-видимому, вызывают повреждение кучи

В чем проблема с этим кодом? Он сбрасывается каждый раз.

Однократно это неудавшееся утверждение «_ASSERTE (_CrtIsValidHeapPointer (pUserData));», в других случаях это просто «ошибка corppuption».

Изменение размера буфера влияет на эту проблему каким-то странным образом — иногда она вылетает на «realloc», а иногда и на «free».

Я отлаживал этот код много раз, и нет ничего ненормального в отношении указателей.

Вы разрушаете кучу. realloc может свободно выбирать, чтобы вернуть вам память из совершенно другого места по мере ее перераспределения, и это аннулирует ваш ptr . Установите ptr после перераспределения.

На второй итерации цикла здесь находятся значения

  • data указывает на буфер размера sizeof(buf)
  • size имеет значение sizeof(buf)

Учитывая эти значения, значение ptr заключается в том, что оно указывает конец буфера, выделенного в data . Это память, не принадлежащая процессу, и следующая операция memcpy записывает это и повреждает память.

Убрать nbsp из строки

Здравствуйте. Достался тут проект на CodeInteger . Есть строка в БД (имя категории товара например). При выводе вместо пробела вставляется &nbsp . Что я уже только не пробовал: str_replace , preg_replace и т.д. Но она все равно остается! В БД обычный пробел. Подскажите пожалуйсте, откуда она может браться и как избавиться?

Это нужно исправить, чтоб был перенос на новую строку:
скриншот


UPDATE:
К строке на английском не добавляется. Там обычный пробел. Что-то с кодировкой

6 ответов 6

Тоже долго искал ответ на этот вопрос, в моём случае помогла такая конструкция, может кому-нибудь пригодиться.

Как всегда мучаешься-мучаешься и как только спросишь, сам ответ находишь. Переписал ручками строки в БД и все стало как надо. MySql Workbench значит тоже &nbsp как пробел показывает.
Скорее всего при добавлении из админки стоит htmlspecialchars и уже в БД летят &nbsp вместо пробелов.

При выводе вместо пробела вставляется &nbsp. Но она все равно остается! В БД обычный пробел.

Десятичный код неразрывного пробела 160, шестнадцатеричный 0xA0. Именно этот символ надо заменять.

А мнемоника появляется, скорее всего, уже при формировании разметки.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками php codeigniter или задайте свой вопрос.

Похожие

Подписаться на ленту

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

дизайн сайта / логотип © 2020 Stack Exchange Inc; пользовательское содержимое попадает под действие лицензии cc by-sa 4.0 с указанием ссылки на источник. rev 2020.11.11.35402

ОБРАБОТКА СИМВОЛЬНЫХ И СТРОКОВЫХ ДАННЫХ

Символьный тип– это тип данных, используемый для описания отдельных символов (знаков, букв, кодов). Традиционная запись символьного значения представляет собой символ, заключенный в апострофы, например ‘a’.

Строковая константа представляет собой последовательность символов, заключенную в кавычки (“), например, “это моя первая строка”. В конце каждого строкового типа данного компилятором добавляется нулевой символ, представляемый управляющей константой \0.

Строковая переменная описывается как char st[12], в квадратных скобках указывается длина строки. Это означает, что строковая переменная – массив символов, причем нумерация элементов массива начинается с нуля, и число элементов массива равно числу символов плюс единица.

Операция конкатенации строк обозначается обратной косой чертой:

ная строка символов”

Для описания строковой переменной можно воспользоваться понятием массива символов или указателя (ссылки) на массив символов, например:

char mas[15]; char*st;

Для ввода/вывода символьных/строковых типов данных можно воспользоваться не только функциями printf() и scanf(), но и рядом других функций, прототипы которых описаны библиотечном файле stdio.h:

— функция int putchar(char c) выводит одиночный символ в стандартный выходной поток stdout без последующего перехода к новой строке.

— функция int getchar() применяется для ввода очередного символа из стандартного входного потока stdin. Эта функция не требует задания параметра и принимает значение целого типа, например:

— функция int puts(char *st) используется для вывода строки символов в стандартный выходной поток. Строка символов дополняется символом конца строки ‘\n’, и после вывода строки символов функцией puts() переход к новой строке происходит автоматически.

— функция char *gets(char *st) выполняет считывание из стандартного входного потока. Ввод продолжается до тех пор, пока не будет нажата клавиша Enter. Эта функция дополняет строку введенных символов символом ‘\0’.

Если число представлено в виде строки символов, то можно воспользоваться функциями int atoi (строка символов), long atoll (строка символов), double atof (строка символов) для преобразования строки, соответственно в целое, длинное целое или вещественное число. Прототипы этих функций описаны в библиотечном файле stdlib.h. Если строка не может быть преобразована в число, то возвращается нуль.

В языке С++ существует группа операторов для работы с символьными переменными (табл. 8), которые описаны в загрузочном файле ctype.h.

Операторы, для работы с символьными переменными

Синтаксис Описание
isalpha(c) Функция проверяет, является ли символ с буквой
isdigit(c) Функция проверяет, является ли символ с цифрой
islower(c) Функция проверяет, является ли символ с строчной буквой
isspace(c) Функция проверяет, является ли символ с пустым символом (пробел, табуляция или новая строка)
isupper(c) Функция проверяет, является ли символ с прописной буквой
isalnum(c) Функция проверяет, является ли символ с алфавитноцифром (буква или цифра)
isascii(c) Функция проверяет, является ли символ с кодом ASCII(0-127)
iscntrl(c) Функция проверяет, является ли символ с управляющим символом
ispunct(c) Функция проверяет, является ли символ с знаком пунктуации
toupper(c) Функция преобразует символ с в прописную букву
tolower(c) Функция преобразует символ с в строчную букву

Для работы со строкой символов в файле string.h описаны прототипы ряда функций. Библиотека представляет возможности копирования (strcpy, strncpy), сравнения (strcmp, strncmp), объединения строк (strcat, strncat), поиска подстроки (strstr), поиска вхождения символа (strchr, strrchr, strpbrk), определения длины строки (strlen) и т.д., а также содержит специальные функции ввода строк и отдельных символов с клавиатуры и из файла.

=”kuku”; // passw – эталонный пароль.

// Можно описать как *passw=”kuku”

printf (“\n Размер x=%d”, sizeof(x));

printf (“\n Значение указателей x=%u v=%u &i=%u”, x, v, &i);

printf (“\n Значение по адресу x=%d”, *x);

printf (“\n Адрес s=%u”, &s);

При работе с динамическими переменными типа указатель распространенной ошибкой является то, что при описании указатель не инициализируется, что приводит к тому, что компилятор резервирует память только для хранения адреса. В примере, приведенном выше, под переменные *х и *f отводится только два байта под хранение адреса и не отводится два байта под хранение переменной типа int. Что может привести к попытке размещения в процессе выполнения программы значения переменной по начальному адресу, связанному с какой-либо частью операционной системы.

При работе с переменными типа указатель следует придерживаться следующего алгоритма:

1. Предварительно инициализировать указатель, т.е. присвоить указателю адрес предварительно инициализированной переменной, как это выполнено в примере для указателя *v.

2. Использовать функции распределения динамической памяти – «кучи», описанные в библиотечном файле alloc.h:

void *malloc(unsigned s) – выделение памяти размером s байт;

void *calloc (unsigned nel, unsigned els) – выделение памяти для nel элементов, для которых размер каждого элемента определяется переменной els,

void *realloc(void *blok, unsigned s) – изменение размера ранее выделенной памяти для блока *block до величины s байт;

void far *farcalloc(unsigned long nel, unsigned long els) – выделение памяти для дальней ее модели из nel элементов, где размер каждого элемента els;

void free (void *blok) – освобождение ранее выделенной памяти.

Все функции имеют тип vo >

Пример. Вычислить сумму n элементов массива вещественного типа.

Илон Маск рекомендует:  Справочное руководство по mysql версии 5 0 0 alpha
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL