Malloc отвести память


Содержание

Программирование: Разработка и отладка программ

Для приложений память выделяется с помощью подсистемы malloc. Подсистема malloc — это API для управления памятью, состоящий из следующих функций:

  • malloc
  • calloc
  • realloc
  • free
  • mallopt
  • mallinfo
  • alloca
  • valloc

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

Подсистема malloc выполняет три базовые операции с памятью: выделение, освобождение и изменение размера области памяти. Для выделения памяти служат функции malloc и calloc , для освобождения — функция free , а для изменения размера — функция realloc . Функции mallopt и mallinfo поддерживаются для совместимости c System V. Функция mallinfo может применяться для получения информации о куче, с которой работает функция malloc , во время создания программы. С помощью функции mallopt можно освобождать память блоками, кратными размеру страницы и выровненными по границе страницы, а также отключить стандартную процедуру распределения памяти. Функция valloc аналогична malloc и поддерживается для совместимости со стандартом Berkeley.

Адресное пространство 32-разрядных прикладных программ разделяется в системе на семь сегментов:

С 0x00000000 по 0x0fffffff Содержит ядро.
С 0x10000000 по 0x1fffffff Содержит текст прикладной программы.
С 0x20000000 по 0x2fffffff Содержит данные и стек прикладной программы.
С 0x30000000 по 0xafffffff Общая память и память служб mmap .
С 0xd0000000 по 0xdfffffff Содержит текст общей библиотеки.
С 0xe0000000 по 0xefffffff Содержит данные ядра.
С 0xf0000000 по 0x0fffffff Содержит данные общей библиотеки приложения.

Адресное пространство 64-разрядных приложений разделяется в системе на семь сегментов:

С 0x0000 0000 0000 0000 по 0x0000 0000 0fff ffff Содержит ядро.
С 0x0000 0000 d000 0000 по 0x0000 0000 dfff ffff Содержит информацию общей библиотеки.
С 0x0000 0000 e000 0000 по 0x0000 0000 efff ffff Содержит данные ядра.
С 0x0000 0000 f000 0000 по 0x0000 0000 0fff ffff Зарезервировано.
С 0x0000 0001 0000 0000 по 0x07ff ffff ffff ffff Содержит текст, данные и стек прикладной программы, а также общую память и память служб mmap .
С 0x0800 0000 0000 0000 по 0x08ff ffff ffff ffff Защищенные объекты.
С 0x0900 0000 0000 0000 по 0x09ff ffff ffff ffff Текст и данные общей библиотеки.
С 0x0f00 0000 0000 0000 по 0x0fff ffff ffff ffff Стек приложения.

На первый байт, следующий после последнего байта данных программы, указывает переменная _edata. Подсистема malloc создает кучу при размещении первого блока данных. Функция malloc создает кучу путем перемещения указателя _edata и освобождения области памяти для кучи с помощью функции sbrk . После этого функция malloc расширяет кучу в соответствии с требованиями приложения. Размер области памяти, добавляемой при расширении кучи, задается переменной BRKINCR . Для просмотра этого значения предназначена функция mallinfo .

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

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

Стандартная стратегия более эффективна для большинства типичных приложений. Отдельные особенности стратегии 3.1 могут дать выигрыш в ряде нестандартных ситуаций. Подробные сведения об этом приведены в разделе Различия между стандартной стратегией и стратегией 3.1. Стратегию 3.1 можно применять только с 32-разрядными приложениями. Она не работает в 64-разрядной среде.

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

С помощью функции roundup подсчитывается размер запрошенного блока в байтах. Формат:

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

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

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

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

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

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

После этого все 32-разрядные программы, запущенные из данной оболочки, будут применять стратегию распределения памяти 3.1 (63-разрядные по-прежнему будут пользоваться стандартной стратегией). Если переменной MALLOCTYPE будет присвоено любое другое значение, то будет применяться стандартная стратегия.

В стратегии 3.1 куча представляет собой набор из 28 хэш-блоков, каждый из которых указывает на список. Хэширование — это способ преобразования ключа поиска в адрес с целью чтения или записи элемента данных. Такой способ позволяет минимизировать среднее время поиска. Хэш-блок содержит одно или несколько полей, хранящих результат операции. Каждый список содержит блоки определенного размера. Индекс хэш-блока определяет размер блоков в связанном с ним списке. Размер блока вычисляется по следующей формуле:

где i — это номер хэш-блока. Это означает, что нулевой хэш-блок ссылается на список из блоков 2 0+4 = 16 байт длиной. Если предположить, что префикс занимает 8 байт, такие блоки могут применяться для запросов на область памяти размером от 0 до 8 байт. В приведенной ниже таблице показана взаимосвязь между размером запрошенной области памяти и хэш-блоками.

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








Стратегия 3.1
Хэш-блок Размер блока Диапазон запросов Число требуемых страниц
16 0 . 8
1 32 9 . 24
2 64 25 . 56
3 128 57 . 120
4 256 121 . 248
5 512 249 . 504
6 1 Kб 505 . 1 Кб — 8
7 2 Кб 1 Кб — 7 . 2 Кб — 8
8 4 Кб 2 Кб — 7 . 4 Кб — 8 2
9 8 Кб 4 Кб-7 . 8 Кб — 8 3
10 16 Кб 8 Кб — 7 . 16 Кб — 8 5
11 32 Кб 16 Кб — 7 . 32 Кб — 8 9
12 64 Кб 32 Кб — 7 . 64 Кб — 8 17
13 128 Кб 64 Кб-7 . 128 Кб-8 33
14 256 Кб 128 Кб-7 . 256 Кб-8 65
15 512 Кб 256 Кб-7 . 512 Кб-8 129
16 1 Мб 256 Кб-7 . 1 Мб-8 257
17 2 Мб 1 Мб-7 . 2 Мб-8 513
18 4 Мб 2 Мб-7 . 4 Мб-8 1 Кб + 1
19 8 Мб 4 Мб-7 . 8 Мб-8 2 Кб + 1
20 16 Мб 8 Мб-7 . 16 Мб-8 4 Кб + 1
21 32 Мб 16 Мб-7 . 32 Мб-8 8 Кб + 1
22 64 Мб 32 Мб-7 . 64 Мб-8 16 Кб + 1
23 128 Мб 64 Мб-7 . 128 Мб-8 32 Кб + 1
24 256 Мб 128 Мб-7 . 256 Мб-8 64 Кб + 1
25 512 Мб 256 Мб-7 . 512 Мб-8 128 Кб + 1
26 1024 Мб 512 Мб-7 . 1024 Мб-8 256 Кб + 1
27 2048 Мб 1024 Мб-7 . 2048 Мб-8 512 Кб + 1

Перед выделением блока из пула свободных блоков число запрошенных байт преобразуется в индекс массива хэш-блоков по следующей формуле:

Размер блоков в списке, на который ссылается хэш-блок, можно вычислить по формуле: размер блока = 2 хэш-блок + 4 . Если список, на который ссылается хэш-блок, пустой, то в него добавляются блоки путем выделения памяти с помощью функции sbrk . Если размер блока меньше страницы, то функция sbrk выделяет страницу. При этом число блоков, добавленных в список, равно размеру страницы, поделенному на размер блока. Если размер блока больше либо равен размеру страницы, необходимый объем памяти выделяется с помощью функции sbrk , и к списку свободных блоков хэш-блока добавляется всего один блок. Если список свободных блоков не пуст, то инициатору возвращается первый блок списка. При этом указатель на начало списка связывается со следующим блоком.

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

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

Стратегию 3.1 можно применять только с 32-разрядными приложениями. Даже если для 64-разрядной программы будет указано значение MALLOCTYPE=3.1, для нее будет применяться стандартная стратегия.

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

  • Сложная куча malloc
  • Блоки malloc
  • Отладчик malloc

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

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

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

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

Почему malloc () выделяет больше памяти, чем требуется, и как я могу отключить malloc в Mac OS X?

Я делаю программу для робота, который хранит лабиринт в виде многомерного динамического массива смежности, и, поскольку узлы будут обнаруживаться вдоль обхода лабиринта, я пытаюсь выделить для него начальную память, а затем перераспределить память после нового узла найден. Однако однажды я понял, что храню больше данных, чем выделил с помощью malloc (Я забыл использовать realloc ) и, несмотря на это, это не давало мне какой-либо ошибки сегментации или какой-либо другой ошибки. Поэтому мне было интересно узнать:

  1. Почему malloc выделяет больше памяти, чем требуется?
  2. Как я могу отключить это, чтобы я мог правильно отлаживать свою программу? Я уверен, что у робота не будет такой большой кучи памяти, как у моего MacBook с 16 ГБ ОЗУ, поэтому я хотел бы знать, работает ли моя программа в той памяти, которую я выделил ей в программе.

Благодарю за ваш ответ!

Решение

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

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

В дополнение к вышесказанному, malloc сам по себе не выделяет любой размер, который вы пожелаете. Обычно он выделяет чанки по 8, 16 или 32 байта в зависимости от структуры данных, используемой для управления кучей. Когда вы выделяете 1 байт с помощью malloc, вы фактически получаете около 15 байт, которые никогда не будут использоваться никем. Перезапись этих байтов не вызовет заметного вреда.

Если вы хотите убедиться, что ваша программа работает с небольшим объемом памяти, rlimit / ulimit и co — ваши друзья. Если вы хотите убедиться, что вы не превышаете свои выделенные буферы, я настоятельно рекомендую очиститель адресов. Компилировать (как clang, так и gcc) с -fsanitize=address и ваша программа вылетит, как только вы переступите через свои буферы. Конечно, это связано с затратами на производительность.

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

Другие решения

Это не так, вам просто повезло. Ваша программа вызвала неопределенное поведение, поэтому могло произойти все что угодно.

Если вы хотите запустить вашу программу в стесненной среде, имитировать тот факт, что ваша целевая машина имеет ограниченную память, вы можете. Перед запуском вашей программы в вашей оболочке выполните следующую команду: ulimit -Sv 1000 , Это в килобайтах, поэтому 1000 означает 1 мегабайт.

Подробнее об ulimit смотрите здесь: http://ss64.com/osx/ulimit.html — Вы можете использовать его и в Linux.

Вы также можете использовать функцию C setrlimit() делать то же самое в вашей программе, если вы не хотите бегать ulimit каждый раз.

Реклама

Инсталлятор CreateInstall
Бесплатные и коммерческие инсталляторы

Скриптовый язык программирования Gentee — Бесплатный, кроссплатформенный язык программирования для автоматизации.

malloc

Отвести память. Функция отводит память указанного размера.

Параметры

size Размер отводимого блока памяти.

Возвращаемое значение

Указатель на отведенный блок памяти или 0 в случае ошибки.

malloc malloc

Размещение блоков памяти Allocates memory blocks.

Синтаксис Syntax

Параметры Parameters

size size
Байты для размещения. Bytes to allocate.

Возвращаемое значение Return Value

Функция malloc возвращает указатель void на выделенное пространство или значение NULL , если объем доступной памяти недостаточен. malloc returns a void pointer to the allocated space, or NULL if there is insufficient memory available. Чтобы получить указатель на тип, отличный от void, используйте приведение типа к возвращаемому значению. To return a pointer to a type other than void, use a type cast on the return value. Дисковое пространство, на который указывает возвращаемое значение, будет гарантированно соответствовать требованиям к выравниванию для хранения объектов любого типа, если таковые требования не превышают базовые. The storage space pointed to by the return value is guaranteed to be suitably aligned for storage of any type of object that has an alignment requirement less than or equal to that of the fundamental alignment. (В визуальном C++элементе основное выравнивание — это выравнивание, необходимое для типа Doubleили 8 байт. (In Visual C++, the fundamental alignment is the alignment that’s required for a double, or 8 bytes. В коде для 64-разрядных платформ это ограничение составляет 16 байтов.) Используйте _aligned_malloc , чтобы выделить хранилище для объектов с более высоким требованием выравнивания, например типы SSE __m128 и __m256, и типы, объявленные с помощью __declspec(align( n )) , где n больше 8. In code that targets 64-bit platforms, it’s 16 bytes.) Use _aligned_malloc to allocate storage for objects that have a larger alignment requirement—for example, the SSE types __m128 and __m256, and types that are declared by using __declspec(align( n )) where n is greater than 8. Если Размер равен 0, функция malloc выделяет элемент нулевой длины в куче и возвращает допустимый указатель на этот элемент. If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item. Всегда проверяйте возврат от malloc, даже если требуемый объем памяти мал. Always check the return from malloc, even if the amount of memory requested is small.

Примечания Remarks

Функция malloc выделяет блок памяти размером не меньше байт. The malloc function allocates a memory block of at least size bytes. Блок может быть больше, чем Размер байтов, из-за пространства, необходимого для выравнивания и сведений об обслуживании. The block may be larger than size bytes because of the space that’s required for alignment and maintenance information.

malloc переводится в еномем , если выделение памяти завершается ошибкой или если объем запрошенной памяти превышает _HEAP_MAXREQ. malloc sets errno to ENOMEM if a memory allocation fails or if the amount of memory requested exceeds _HEAP_MAXREQ. Дополнительные сведения об этих и других кодах ошибок см. в разделе errno, _doserrno, _sys_errlist и _sys_nerr. For information about this and other error codes, see errno, _doserrno, _sys_errlist, and _sys_nerr.

Код запуска использует malloc для выделения хранилища для переменных _environ, envpи argv . The startup code uses malloc to allocate storage for the _environ, envp, and argv variables. Следующие функции и их аналоги в расширенных символах также вызывают функцию malloc. The following functions and their wide-character counterparts also call malloc.

Функция _set_new_mode C++ задает новый режим обработчика для malloc. The C++ _set_new_mode function sets the new handler mode for malloc. Новый режим обработчика указывает, что в случае сбоя malloc вызывает новую подпрограммы обработчика, заданную _set_new_handler. The new handler mode indicates whether, on failure, malloc is to call the new handler routine as set by _set_new_handler. По умолчанию malloc не вызывает новую подпрограммы обработчика при сбое выделения памяти. By default, malloc does not call the new handler routine on failure to allocate memory. Это поведение по умолчанию можно переопределить таким образом, что, когда malloc не сможет выделить память, malloc вызывает новую подпрограммы обработчика так же, как и оператор New в случае сбоя по той же причине. You can override this default behavior so that, when malloc fails to allocate memory, malloc calls the new handler routine in the same way that the new operator does when it fails for the same reason. Чтобы переопределить значение по умолчанию _set_new_mode(1) , вызовите на раннем этапе программы или свяжите с использованием NEWMODE. OBJ (см. раздел Параметры связи). To override the default, call _set_new_mode(1) early in your program, or link with NEWMODE.OBJ (see Link Options).

Если приложение связано с отладочной версией библиотек времени выполнения C, malloc разрешается в _malloc_dbg. When the application is linked with a debug version of the C run-time libraries, malloc resolves to _malloc_dbg. Дополнительные сведения об управлении кучей в процессе отладки см. в разделе Сведения о куче отладки CRT. For more information about how the heap is managed during the debugging process, see CRT Debug Heap Details.

malloc отмечается __declspec(noalias) и __declspec(restrict) ; это означает, что функция гарантированно не изменяет глобальные переменные и что возвращаемый указатель не имеет псевдонима. malloc is marked __declspec(noalias) and __declspec(restrict) ; this means that the function is guaranteed not to modify global variables, and that the pointer returned is not aliased. Дополнительные сведения см. в разделах noalias и restrict. For more information, see noalias and restrict.

Требования Requirements

Подпрограмма Routine Обязательный заголовок Required header
malloc malloc и and

Дополнительные сведения о совместимости см. в разделе Совместимость. For additional compatibility information, see Compatibility.

Malloc отвести память

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

Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.

Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h :

malloc() . Имеет прототип

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

calloc() . Имеет прототип

Выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL

realloc() . Имеет прототип

Изменяет размер ранее выделенного блока памяти, на начало которого указывает указатель bl, до размера в ns байт. Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc

free() . Имеет прототип

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

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

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

Консольный вывод программы:

Здесь для управления памятью для массива определен указатель block типа int . Количество элементов массива заранее неизвестно, оно представлено переменной n.

Вначале пользователь вводит количество элементов, которое попадает в переменную n. После этого необходимо выделить память для данного количества элементов. Для выделения памяти здесь мы могли бы воспользоваться любой из трех вышеописанных функций: malloc, calloc, realloc. Но конкретно в данной ситуации воспользуемся функцией malloc :

Прежде всего надо отметить, что все три выше упомянутые функции для универсальности возвращаемого значения в качестве результата возвращают указатель типа void * . Но в нашем случае создается массив типа int, для управления которым используется указатель типа int * , поэтому выполняется неявное приведение результата функции malloc к типу int * .

В саму функцию malloc передается количество байтов для выделяемого блока. Это количество подсчитать довольно просто: достаточно умножить количество элементов на размер одного элемента n * sizeof(int) .

После выполнения всех действий память освобождается с помощью функции free() :

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

И если мы попытаемся это сделать, то получим неопределенные значения.

Вместо функции malloc аналогичным образом мы могли бы использовать функцию calloc() , которая принимает количество элементов и размер одного элемента:

Либо также можно было бы использовать функцию realloc() :

При использовании realloc желательно (в некоторых средах, например, в Visual Studio, обязательно) инициализировать указатель хотя бы значением NULL.

Но в целом все три вызова в данном случае имели бы аналогичное действие:

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

Переменная table представляет указатель на массив указателей типа int* . Каждый указатель table[i] в этом массиве представляет указатель на подмассив элементов типа int , то есть отдельные строки таблицы. А переменная table фактически представляет указатель на массив указателей на строки таблицы.

Для хранения количества элементов в каждом подмассиве определяется указатель rows типа int . Фактически он хранит количество столбцов для каждой строки таблицы.

Сначала вводится количество строк в переменную rowscount . Количество строк — это количество указателей в массиве, на который указывает указатель table . И кроме того, количество строк — это количество элементов в динамическом массиве, на который указывает указатель rows . Поэтому вначале необходимо для всех этих массивов выделить память:

Далее в цикле осуществляется ввод количества столбцов для каждый строки. Введенное значение попадает в массив rows. И в соответствии с введенным значением для каждой строки выделяется необходимый размер памяти:

Затем производится ввод элементов для каждой строки.

В конце работы программы при выводе происходит освобождение памяти. В программе память выделяется для строк таблицы, поэтому эту память надо освободить:

И кроме того, освобождается память, выделенная для указателей table и rows:

В чем разница между malloc() и calloc() а также free() и dellete()?

Допустим мы выделаем память

У них есть 2 разницы
calloc выделяет память под массив данных, предварительно инициализируя её нулями. принимает 2 аргумента.
malloc выделяет память без инициализации. принимает один аргумент.

Но в принципе оба эти функций делают одно и тоже, в чем их разница ?
Где то читал что calloc создает ячейки определенного типа и обединяет их, к примеру int может быть 4 байта, и если мы напишем
int *y = (int*) calloc(3, sizeof(int));
То он создаст 12 ячеек, 4 4 4 и в каждом из 4 байтов будет храниться цифра 0.

int *x = (int*) malloc(12);
Тоже создаст 12 ячеек но если к примеру, в него добавим первый 4 байт цифру 9, в другой байт «string», а в другой вообще bool, как он определит что в нем находиться ?

Если в случае с calloc он знает что через каждые 4 байта у него есть цифра, то как в malloc он определит, что через каждые разные количество байтов у него разные количество элементов, Или он определяет свои элементы совсем по другому, в этапе компиляций и т.д.?

  • Вопрос задан более года назад
  • 960 просмотров

Разница только в том, что calloc обнуляет выделенную память перед тем, как возвратить указатель, а malloc этого не делает. Внутри calloc, наверняка вызывает malloc для выделения памяти, а потом memset для обнуления. Так что calloc это просто надстройка над malloc для удобства. Вот схематично реализация calloc:

Сами эти функции оперируют исключительно размером выделяемой области в байтах, им все равно что вы в дальнейшем будете делать с выделенной памятью — инты туда писать или «стринги».
Ну и как бы никто не мешает вам одну и ту же область памяти сначала использовать как массив интов, а потом как массив байт. Или так например:

Пример не использует malloc/calloc для выделения памяти, память выделяется в стеке просто объявлением int a. Тут я попытался показать, что содержимое памяти можно интерпретировать как угодно, главное находится в границах выделенного диапазона.
Причем языки С/С++/asm это позволяют делать, а другие — нет.
Пример предполагает, что int имеет размер 32 бита, не для всех платформ это так, но в основном — именно так.
Кстати этот пример можно использовать для определения порядка байтов платформы: если выведется «1 2 3 4» значит у вас LITTLE ENDIAN, а если «2 1 4 3» — BIG ENDIAN.

PS: free() — это Си, а delete — C++

А free и delete ведут себя идентично ?
Оба ставят метку на область памяти, и считают эту область мусором или все данные в этой области становятся нулями ?
И еще delete вызывает деструктор, а free нет тогда как он освобождает память ?
В некоторых руководствах пишут, что

Так значит free просто отнимает память ?

DEATH2298, Вы когда-нибудь в деструкторе уничтожали память выделенную для этого же объекта? Не уничтожали, потому что объект и деструктор не могут знать как именно для объекта выделена память: объект находится в стеке или в динамической памяти. Память занимаемая объектом всегда уничтожается вне объекта, так же как и выделяется. В деструкторе вы можете уничтожить память, занимаемую членами данных объекта, но не самим объектом.

Для данной конкретной ОС и настроек компилятора по значению this можно предположить где выделена память, но в общем случае — нет. Кроме того, память может быть выделена не для одного объекта (деструктор которого вызван), а для массива объектов. Что вы будете освобождать в этом случае?
Поэтому вызов деструктора в delete здесь не к месту упомянут. И да, в С++ используется именно delete потому что на нем завязана дополнительная функциональность по вызову деструктора.

Да, free просто освобождает память.
Вызов realloc в вашем примере освободит память выделенную для х и вернет нулевой указатель.

Есть некоторое количество разных реализаций менеджеров памяти, которые по разному реализуют malloc/free. В своем проекте вы можете использовать сторонний менеджер памяти, а не тот что предлагается по умолчанию. Кроме того в ОС есть собственный менеджер памяти и можно использовать его. В винде это функции LocalAlloc/LocalFree и еще пачка других.

И еще вот такой вопрос, касательно malloc`a и calloc`

Допустим память выделяется вот так

в calloc храним только Int → 1, 2, 4, 3 каждое из которых 4 байт = 16 байт.
А в malloc храним int 1 → char a, b → double int 5, получается у нас 4байт + 2 + 8 = 14 байт.

Вот как тут компилятор, процессор или не знаю что) понимает что именно находится в этих ячеек ?

Если предположить с логикой, то можно понять что calloc уже знает что через каждые 4 байта у него внутри число типа int. (Я точно не знаю так ли это работает)

Тогда как malloc не знает через сколько шагов что именно у него внутри.

Вот как тут компилятор, процессор или не знаю что) понимает что именно находится в этих ячеек ?

Почему важно проверять, что вернула функция malloc

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

Примечание. В статье под функцией malloc часто будет подразумеваться, что речь идёт не только именно об этой функции, но и о calloc , realloc , _aligned_malloc , _recalloc , strdup и так далее. Не хочется загромождать текст статьи, постоянно повторяя названия всех этих функций. Общее у них то, что они могут вернуть нулевой указатель.

malloc

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

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

Если функция malloc не смогла выделить память, то вряд ли моя программа продолжит функционировать должным образом. Скорее всего, памяти будет не хватать и для других операций, поэтому можно вообще не заморачиваться об ошибках выделения памяти. Первое же обращение к памяти по нулевому указателю приведёт к генерации Structured Exception в Windows, или процесс получит сигнал SIGSEGV, если речь идёт о Unix-подобных системах. В результате программа упадёт, что меня устраивает. Раз нет памяти, то и нечего мучаться. Как вариант, можно перехватить структурное исключение/сигнал и обрабатывать разыменовывания нулевого указателя более централизовано. Это удобнее, чем писать тысячи проверок.

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

Кстати, существует ещё одно оправдание разработчиков, почему они не проверяют, что вернула функция malloc . Функция malloc только резервирует память, но вовсе нет гарантии, что хватит физической памяти, когда мы начнём использовать выделенный буфер памяти. Поэтому, раз всё равно гарантии нет, то и проверять не надо. Например, именно так Carsten Haitzler, являющийся одним из разработчиков библиотеки EFL Core, объяснял, почему я насчитал более 500 мест в коде библиотеки, где отсутствуют проверки. Вот его комментарий к статье:

OK so this is a general acceptance that at least on Linux which was always our primary focus and for a long time was our only target, returns from malloc/calloc/realloc can’t be trusted especially for small amounts. Linux overcommits memory by default. That means you get new memory but the kernel has not actually assigned real physical memory pages to it yet. Only virtual space. Not until you touch it. If the kernel cannot service this request your program crashes anyway trying to access memory in what looks like a valid pointer. So all in all the value of checking returns of allocs that are small at least on Linux is low. Sometimes we do it. sometimes not. But the returns cannot be trusted in general UNLESS its for very large amounts of memory and your alloc is never going to be serviced — e.g. your alloc cannot fit in virtual address space at all (happens sometimes on 32bit). Yes overcommit can be tuned but it comes at a cost that most people never want to pay or no one even knows they can tune. Secondly, fi an alloc fails for a small chunk of memory — e.g. a linked list node. realistically if NULL is returned. crashing is about as good as anything you can do. Your memory is so low that you can crash, call abort() like glib does with g_malloc because if you can’t allocate 20-40 bytes . your system is going to fall over anyway as you have no working memory left anyway. I’m not talking about tiny embedded systems here, but large machines with virtual memory and a few megabytes of memory etc. which has been our target. I can see why PVS-Studio doesn’t like this. Strictly it is actually correct, but in reality code spent on handling this stuff is kind of a waste of code given the reality of the situation. I’ll get more into that later.

Приведённые рассуждения программистов являются неправильными, и ниже я подробно объясню почему. Но прежде надо ответить на вопрос: «а причём здесь Chromium?».

Chromium

Chromium здесь при том, что в используемых в нём библиотеках имеется не менее 70 ошибок, связанных с отсутствием проверки после вызова таких функций, как malloc , calloc , realloc . Да, в самом Chromium эти функции почти нигде не используются. В Chromium применяются только контейнеры или operator new . Однако, раз ошибки есть в используемых библиотеках, то значит, можно сказать, что они есть и в Chromium. Конечно, какие-то части библиотек могут не использоваться при работе Chromium, но определять это сложно и ненужно. Всё равно надо править все ошибки.

Я не буду приводить в статье множество фрагментов кода с ошибками, так как они однотипны. Приведу для примера только одну ошибку, обнаруженную в библиотеке Yasm:

Предупреждение PVS-Studio: V522 CWE-690 There might be dereferencing of a potential null pointer ‘r’. Check lines: 52, 51. substr.h 52

В коде нет никакой защиты от нулевого указателя. Другие подобные ошибки из Chromium и используемых библиотек я собрал вместе в файл и выложил их здесь: chromium_malloc.txt. В файле упоминаются 72 ошибки, но на самом деле их может быть больше. Как я писал в вводной статье, я просматривал отчёт только поверхностно.

Согласно Common Weakness Enumeration обнаруженные ошибки PVS-Studio классифицирует как:

  • CWE-690: Unchecked Return Value to NULL Pointer Dereference.
  • CWE-628: Function Call with Incorrectly Specified Arguments.
  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer

Как видите, даже в таком высококачественном проекте как Chromium, можно заметить массу дефектов, связанных с отсутствием проверок. Теперь я перехожу к самому интересному и расскажу, почему проверки обязательно нужны.

Почему обязательно нужна проверка

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

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


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

В разных операционных системах для этих целей резервируется разное количество памяти. При этом в некоторых ОС это значение можно настраивать. Поэтому нет смысла называть какое-то конкретное число зарезервированных байт памяти. Но чтобы как-то сориентировать читателя, скажу, что в Linux системах типовым значением является 64 Кбайт.

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

Заваривайте кофе, мы начинаем!

Разыменовывание нулевого указателя — это неопределённое поведение

С точки зрения языка C и C++ разыменовывание нулевого указателя приводит к неопределенному поведению. Неопределённое поведение — это что угодно. Не думайте, что вы знаете, как будет вести себя программа, если произойдёт разыменовывание nullptr . Современные компиляторы занимаются серьезными оптимизациями, в результате чего бывает невозможно предсказать, как проявит себя та или иная ошибка в коде.

Неопределённое поведение программы — это очень плохо. Вы не должны допускать его в своём коде.

Не думайте, что сможете совладать с разыменовыванием нулевого указателя, используя обработчики структурных исключений (SEH в Windows) или сигналы (в UNIX-like системах). Раз разыменовывание нулевого указателя было, то работа программы уже нарушена, и может произойти что угодно. Давайте рассмотрим абстрактный пример, почему нельзя полагаться на SEH-обработчики и т.п.

Этот код заполняет массив от краёв к центру. К центру значения элементов увеличиваются. Это придуманный за 1 минуту пример, поэтому не гадайте, зачем такой массив кому-то нужен. Я и сам не знаю. Мне было важно, чтобы в соседних строках программы происходила запись в начало массива и куда-то в его конец. Такое иногда бывает нужно и в практических задачах, и мы рассмотрим реальный код, когда доберёмся до 4-ой причины.

Ещё раз внимательно посмотрим на эти две строки:

С точки зрения программиста, в начале цикла произойдёт запись в элемент ptr[0], и возникнет структурное исключение/сигнал. Оно будет обработано, и всё будет хорошо.

Однако компилятор в каких-то целях оптимизации может переставить присваивания местами. Он имеет на это полное право. С точки зрения компилятора, если указатель разыменовывается, то он не может быть равен nullptr . Если указатель нулевой, то это неопределённое поведение, и компилятор не обязан думать о последствиях оптимизации.

Так вот, компилятор может решить, что в целях оптимизации выгоднее выполнить присваивания так:

В результате в начале произойдет запись по адресу ((size_t *)nullptr)[N * 2 — 0 — 1] . Если значение N достаточно велико, то страница защиты в начале памяти будет «перепрыгнута» и значение переменной i может быть записано в какую-то ячейку, доступную для записи. В общем, произойдёт порча каких-то данных.

И только после этого будет выполнено присваивание по адресу ((size_t *)nullptr)[0] . Операционная система заметит попытку записи в контролируемую ею область и сгенерирует сигнал/исключение.

Программа может обработать это структурное исключение/сигнал. Но уже поздно. Где-то в памяти есть испорченные данные. Причем непонятно, какие данные испорчены и к каким последствиям это может привести!

Виноват ли компилятор, что поменял операции присваивания местами? Нет. Программист допустил разыменовывание нулевого указателя и тем самым ввёл программу в состояние неопределённого поведения. В данном конкретном случае неопределённое поведение программы будет заключаться в том, что где-то в памяти испорчены данные.

Исходите из аксиомы: любое разыменовывание нулевого указателя — это неопределённое поведение программы. Не бывает «безобидного» неопределённого поведения. Любое неопределённое поведение недопустимо.

Не допускайте разыменовывания указателей, которые вернула функция malloc и её аналоги, без их предварительной проверки. Не полагайтесь на какие-то другие способы перехвата разыменовывания нулевого указателя. Следует использовать только старый добрый оператор if .

Разыменовывание нулевого указателя — это уязвимость

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

Одним нормально, если программа из-за разыменовывания нулевого указателя упадёт или если ошибка будет обработана каким-то общим способом с помощью перехвата сигнала/структурного исключения.

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

Не буду голословным. Есть, такая программа как Ytnef, предназначенная для декодирования TNEF потоков, например, созданных в Outlook. Так вот, разработчики приложения считают отсутствие проверки после вызова calloc уязвимостью CVE-2020-6298.

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

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

Однако, если вы разрабатываете какую-то библиотеку, то отсутствие проверок недопустимо! Вашей библиотекой могут пользоваться не только ленивые программисты, создающие безответственные приложения, типа игры Тетрис. Надо заботиться и о нормальных программистах, и нормальных программах.

Поэтому я идеологически не согласен, например, с Carsten Haitzler, что в библиотеке EFL Core нет проверок (подробности в статье). Это не позволяет построить на основе таких библиотек надёжные приложения.

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

Где гарантии, что будет разыменовывание именно нулевого указателя?

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

Сейчас я на практических примерах покажу, что я имею в виду. Возьмём, например, код из библиотеки LLVM-subzero, которая используется в Chromium. Если честно, я теряюсь в догадках, какая связь между проектом Chromium и LLVM, но она есть.

Предупреждение PVS-Studio: V522 CWE-690 There might be dereferencing of a potential null pointer ‘TheTable’. Check lines: 65, 59. stringmap.cpp 65

Сразу после выделения буфера памяти происходит запись в ячейку TheTable[NumBuckets] . Если значение переменной NumBuckets достаточно большое, то мы испортим какие-то данные с непредсказуемыми последствиями. После такой порчи вообще нет смысла рассуждать, как будет работать программа. Могут последовать самые неожиданнейшие последствия.

Аналогичные опасные присваивания я вижу ещё в двух местах этого проекта:

  • V522 CWE-690 There might be dereferencing of a potential null pointer ‘Buckets’. Check lines: 219, 217. foldingset.cpp 219
  • V769 CWE-119 The ‘NewTableArray’ pointer in the ‘NewTableArray + NewSize’ expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 218, 216. stringmap.cpp 218

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

Продолжу заочную дискуссию с Carsten Haitzler. Он утверждает, что они понимают, что делают, когда не проверяют результат вызова функции malloc . Нет, не понимают. Давайте взглянем, например, на вот такой фрагмент кода из библиотеки EFL:

Предупреждение PVS-Studio: V522 There might be dereferencing of a potential null pointer ‘array’. edje_cc_handlers.c 14249

Примечание . Я использую старые исходники EFL Core Libraries, которые остались у меня со времён написания статьи про эту библиотеку. Поэтому код или номера строк могут уже не соответствовать тому, что есть сейчас. Однако это не важно для повествования.

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

Если это произойдёт, то вовсе не обязательно возникнет структурное исключение/сигнал из-за разыменовывания нулевого указателя. Взглянем вот на эти строчки:

Если значение переменной filter->data_count достаточно большое, то значения будут записаны по какому-то непонятному адресу.

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

Я внимательно не стал изучать старый отчёт, касающийся EFL Core Libraries, но это точно не единственная подобная ошибка. Я заметил как минимум ещё два похожих места, где после realloc данные дописываются по какому-то индексу.

Я вновь задаю вопрос: «где гарантии, что будет разыменовывание именно нулевого указателя?». Нет такой гарантий. Невозможно, разрабатывая или модифицируя код, помнить про только что рассмотренный нюанс. Запросто можно что-то испортить в памяти, при этом программа продолжит выполняться как ни в чём не бывало.

Единственный способ написать надёжный и правильный код — это всегда проверять результат, который вернула функция malloc . Проверь и живи спокойно.

Где гарантии, что memset заполняет память в прямом порядке?

Найдётся кто-то, кто скажет что-то подобное:

Я отлично понимаю про realloc и всё остальное, что написано в статье. Но я профессионал и, выделяя память, сразу заполняю её нулями с помощью memset . Там, где действительно необходимо, я использую проверки. Но лишние проверки после каждого malloc я писать не буду.

Вообще заполнять память сразу после выделения буфера достаточно странная идея. Странная потому, что есть функция calloc. Тем не менее, так поступают очень часто. Далеко за примером ходить не надо, вот код из библиотеки WebRTC, используемой в Chromium:

Выделяется память, затем буфер заполняется нулями. Очень частая практика, хотя, на самом деле, две строчки можно сократить до одной, используя calloc . Но всё это не важно.

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

Функция memset имеет право начать заполнять буфер с конца. И, если выделялся большой буфер, то могут быть затёрты какие-то полезные данные. Да, заполняя память, функция memset рано или поздно достигнет страницы, защищённой от записи, и операционная система сгенерирует структурное исключение/сигнал. Однако обрабатывать их уже не имеет смысла. К этому моменту будет испорчен большой фрагмент памяти, и дальнейшая работа программы будет непредсказуема.

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

Соглашусь, что подобная реализация memset действительно экзотика, и я даже задавал вопрос на StackOverflow на эту тему. В ответе говорится:

The Linux kernel’s memset for the SuperH architecture has this property: link .

К сожалению, это код на незнакомой мне разновидности ассемблера, поэтому я не берусь рассуждать о нём. Зато ещё есть вот такая интересная реализация на языке Си. Приведу начало это функции:

Обратите внимание на:

Здесь мы возвращаемся к причине N1 «Разыменовывание нулевого указателя — это неопределённое поведение». Нет гарантии, что компилятор в целях оптимизации не поменяет присваивания местами. Если компилятор это сделает, и аргумент n будет иметь большое значение, то вначале будет испорчен какой-то байт памяти. И только потом произойдёт разыменовывание нулевого указателя.

Опять не убедительно? Хорошо, а как вам вот такая реализация:

Нельзя доверять даже функции memset . Да, это во многом искусственная и надуманная проблема. Я просто хотел показать, как много существует нюансов, если не проверять значение указателя. Просто невозможно всё это учесть. Поэтому не надо выпендриваться, а следует аккуратно проверять каждый указатель, который вернула функция malloc и аналогичные ей. И вот именно тогда вы станете профессионалом.

Заключение

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

Как видите, анализатор PVS-Studio совсем не зря предупреждает о том, что нет проверки указателя после вызова malloc . Невозможно написать надёжный код, не делая проверки. Особенно это важно и актуально для разработчиков библиотек.

Надеюсь, теперь вы по-новому взглянете на функцию malloc , проверки указателей в коде и предупреждения анализатора PVS-Studio. Не забудьте показать эту статью своим коллегам и начать использовать PVS-Studio. Желаю всем поменьше багов.

Найдите ошибки в своем C, C++, C# и Java коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

Malloc отвести память

Прошу прошения, если мой вопрос слишком глуп, но в Си я только-только начинаю осваиваться. Имеется функция извлечения слова из строки следующего вида:

char *extractword(const unsigned int n, char *str, char *worddelim)
<
int i,j;
char *word;

word = NULL;
i = wordpos(n, str, worddelim);
if (i == -1) return word;
if (wordpos(n+1, str, worddelim) != -1) j = wordpos(n+1, str, worddelim)-i; else j = 2;
word = (char *) calloc(j+1, sizeof(char));
j = 0;
while ((i

Хм. Может сделать конструктором и деструктором?

wordpos();
<
free(word);
>;
Хотя. Это вроде с++

Хм. Может сделать конструктором и деструктором?
Хотя. Это вроде с++

Вроде да. А без с++ как?

С другой стороны

word — это вроде указатель на char.
т.е. адрес (значение) мы возвращаем и используем.
А сама переменная imho освобождается и так.

Ведь не вознникает вопроса об освобождении i j

SHRDLU
Хм. Интересная конструкция.
Судя по всему, обойти использование динамичсекого выделения не удастся, поэтому в этой конструкции освобождение нужно делать вне вызова функции.
Т.е.
функция extractword возвращает нам указатель на выделенную область, а мы, в свою очередь, после использования строки в функции, вызвавшей extractword, уничтожаем область памяти через полученыый указатель, т.е.

OurPointer=extractword(255, «весна весна весна любовь», ‘ ‘);
printf(OurPointer);
free(OurPointer);

А сама переменная imho освобождается и так.
Ведь не вознникает вопроса об освобождении i j
Здесь речь идет об освобождении области, выделенной calloc’ом

С другой стороны
word — это вроде указатель на char.
т.е. адрес (значение) мы возвращаем и используем.
А сама переменная imho освобождается и так.
Ведь не вознникает вопроса об освобождении i j

Не возникает, переменные уничтожаются автоматически при выходе из функции. А вот распределеная через calloc/malloc память — . скорее всего нет. И непонятно, к чему приведет в итоге неосвобождение памяти. Читал в документациях, что память, выделенную через malloc ОБЯЗАТЕЛЬНО надо освобождать вызовом free, и ТОЛЬКО вызовом free эта память освобождается.
SHRDLU1110011257
SHRDLU
OurPointer=extractword(255, «весна весна весна любовь», ‘ ‘);
printf(OurPointer);
free(OurPointer);

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

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

Проверил. Все работает. Большое спасибо за помощь!

Максимальная память, которую malloc может выделять

Я пытался выяснить, сколько памяти я могу в максимальной степени использовать на своей машине (1 Гб оперативной памяти 160 Гб платформы HD Windows).

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

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

Итак, чтобы подтвердить, я написал небольшую программу в C:

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

Я ждал около часа, и, наконец, мне пришлось отключить компьютер.

  • Предоставляет ли malloc также память из HD?
  • В чем причина такого поведения?
  • Почему в любой момент времени не прерывался цикл?
  • Почему не было отказа в распределении?

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

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

Некоторые вопросы: malloc также выделяет память с жесткого диска?

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

В чем причина такого поведения? Почему в любой момент цикл не прерывался?

Почему не было отказа в распределении?

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

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

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

Я знаю, что эта ветка устарела, но для тех, кто хочет дать ей попробовать себя, используйте этот код, отрезанный

при запуске этот код заполняет одну RAM, пока не будет убит ядром. Использование calloc вместо malloc для предотвращения «ленивой оценки». Идеи, взятые из этой темы: Вопросы памяти Malloc

Этот код быстро заполнил мою оперативную память (4 ГБ), а затем примерно через 2 минуты мой раздел подкачки 20 ГБ до его смерти. 64-разрядный Linux, конечно.

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

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

По умолчанию виртуальная память включена и будет потреблять доступное пространство на HD. Таким образом, ваш тест будет продолжаться до тех пор, пока он не выделит весь объем виртуальной памяти для процесса (2 ГБ на 32-битных окнах) или не заполнит жесткий диск.

Включите в него stdlib и stdio.
Этот экстракт взят из глубоких секретов.

Я действительно не знаю, почему это не удалось, но стоит отметить, что `malloc (4)» на самом деле не может дать вам 4 байта, поэтому этот метод не является точным способом найти максимальный размер кучи.

Я нашел это из своего вопроса здесь.

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

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

Кроме того, аргумент malloc является size_t, а диапазон этого типа — [0, SIZE_MAX], поэтому максимум, который вы можете запросить, — SIZE_MAX, значение которого зависит от реализации и определяется в
.

/proc/sys/vm/overcommit_memory контролирует максимум в Linux

Затем man proc затем описывает, как /proc/sys/vm/overcommit_memory контролирует максимальное распределение:

Этот файл содержит режим учета виртуальной памяти ядра. Значения:

  • 0: эвристический overcommit (по умолчанию)
  • 1: всегда слишком часто, никогда не проверяйте
  • 2: всегда проверяйте, никогда не переусердствуйте

В режиме 0 вызовы mmap (2) с MAP_NORESERVE не проверяются, и проверка по умолчанию очень слабая, что приводит к риску получения процесса «OOM-kill».

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

В режиме 2 (доступно с Linux 2.6) общее виртуальное адресное пространство, которое может быть выделено (CommitLimit в /proc/meminfo), рассчитывается как

  • total_RAM — общий объем оперативной памяти в системе;
  • total_huge_TLB — объем памяти, выделенный для огромных страниц;
  • overcommit_ratio — это значение в /proc/sys/vm/overcommit_ratio; и
  • total_swap — это объем пространства подкачки.

Например, в системе с 16 ГБ физической ОЗУ, 16 ГБ подкачки, без места, выделенного для огромных страниц, и overcommit_ratio, равным 50, эта формула дает CommitLimit 24 ГБ.

Начиная с Linux 3.14, если значение в /proc/sys/vm/overcommit_kbytes отлично от нуля, вместо этого CommitLimit рассчитывается следующим образом:

См. также описание /proc/sys/vm/admiin_reserve_kbytes и /proc/sys/vm/user_reserve_kbytes.

Documentation/vm/overcommit-accounting.rst в дереве ядра 5.2.1 также дает некоторую информацию, хотя и немного меньше:

Ядро Linux поддерживает следующие режимы обработки overcommit

0 Эвристическая обработка overcommit. Очевидные злоупотребления адресом пространство отказано. Используется для типичной системы. Это обеспечивает серьезно дикое распределение не удается, в то же время позволяя уменьшить использование свопа. корню разрешено выделять чуть больше память в этом режиме. Это значение по умолчанию.

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

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

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

Минимальный эксперимент

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

Скомпилируйте и запустите, чтобы выделить 1 ГБ и 1 ТБ:

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

Я не могу найти точную документацию для 0 (по умолчанию), но на моей машине с 32 ГБ ОЗУ не разрешается выделение 1 ТБ:

Если я включу неограниченный overcommit, однако:

тогда распределение 1TiB работает нормально.

Режим 2 хорошо документирован, но мне лень выполнять точные расчеты, чтобы проверить это. Но я просто укажу, что на практике нам разрешено выделять около:

от общего объема ОЗУ, а по умолчанию overcommit_ratio — 50 , поэтому мы можем выделить около половины общего объема ОЗУ.

VSZ против RSS и убийца нехватки памяти

Пока что мы только что выделили виртуальную память.

Тем не менее, в определенный момент, если вы используете достаточно этих страниц, Linux придется начать убивать некоторые процессы.

SBP-Program

Работа с памятью в C

Для работы с памятью в C используют библиотечные функции: free(), malloc(), calloc(), realloc().

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

_msize

Функция _msize возвращает размер область памяти, выделенной из кучи:

аргумент — указатель на блок памяти. Функция _msize возврашает размер памяти в байтах. size_t — это unsigned integer.

malloc

Функция malloc выделяет область памяти из «кучи» (т.е. свободной области памяти):

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

Пример работы с функцией malloc:

здесь выделено место в памяти для массива, состоящего из двух элементов типа int. Если выделение памяти пошло успешно, то освобождаем эту область памяти с помощью функции free.

calloc

Функция calloc выделяет область памяти и размещает в ней массив, инициализированный нулями:

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

Пример работы с функцией calloc:

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

realloc

Функция realloc меняет размер предварительно выделенной области памяти:

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

Пример работы с функцией realloc:

В примере с помомощью функции malloc выделяем область памяти, далее с помощью функции realloc увеличиваем эту память.

Илон Маск рекомендует:  Что такое код debugger_on
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL