stringtranslate.com

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

Динамическое распределение памяти в языке C относится к выполнению ручного управления памятью для динамического распределения памяти в языке программирования C с помощью группы функций в стандартной библиотеке C , а именно malloc , realloc , calloc , align_alloc и free . [1] [2] [3]

Язык программирования C++ включает эти функции; однако операторы new и delete предоставляют схожую функциональность и рекомендуются авторами этого языка. [4] Тем не менее, есть несколько ситуаций, в которых использование / неприменимо, например, код сборки мусора или чувствительный к производительности код, и может потребоваться комбинация и размещения  вместо оператора более высокого уровня.newdeletemallocnewnew

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

Обоснование

Язык программирования C управляет памятью статически , автоматически или динамически . Переменные статической длительности выделяются в основной памяти, обычно вместе с исполняемым кодом программы, и сохраняются в течение всего срока службы программы; переменные автоматической длительности выделяются в стеке и появляются и исчезают по мере вызова и возврата функций. Для переменных статической и автоматической длительности размер выделения должен быть постоянным во время компиляции (за исключением случая автоматических массивов переменной длины [5] ). Если требуемый размер неизвестен до времени выполнения (например, если данные произвольного размера считываются от пользователя или из файла на диске), то использование объектов данных фиксированного размера неадекватно.

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

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

Первоначальное описание C указывало, что callocи cfreeбыли в стандартной библиотеке, но не malloc. Код для простой реализации модели менеджера хранения для Unix был предоставлен с allocи freeкак функции пользовательского интерфейса и с использованием sbrkсистемного вызова для запроса памяти из операционной системы. [6] В документации Unix 6-го издания allocи указаны freeкак низкоуровневые функции выделения памяти. [7] Процедуры mallocи freeв их современной форме полностью описаны в руководстве Unix 7-го издания. [8] [9]

Некоторые платформы предоставляют библиотечные или встроенные вызовы функций , которые позволяют динамическое выделение памяти во время выполнения из стека C, а не из кучи (например, alloca()[10] ). Эта память автоматически освобождается, когда вызывающая функция завершается.

Обзор функций

Функции динамического выделения памяти в языке C определены в stdlib.hзаголовке ( cstdlibheader в C++). [1]

Различия между malloc()иcalloc()

Пример использования

Создать массив из десяти целых чисел с автоматической областью действия в языке C просто:

массив целых чисел [ 10 ]; 

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

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

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

Поскольку mallocобработка запроса может оказаться невозможной, он может вернуть нулевой указатель , и хорошей практикой программирования является проверка на это:

int * array = malloc ( 10 * sizeof ( int )); если ( array == NULL ) { fprintf ( stderr , "malloc failed \n " ); вернуть -1 ; }             

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

бесплатно ( массив );

Память, выделенная с помощью , mallocне инициализирована и может содержать cruft : остатки ранее использованных и отброшенных данных. После выделения с помощью mallocэлементы массива являются неинициализированными переменными . Команда callocвернет выделение, которое уже было очищено:

int * array = calloc ( 10 , sizeof ( int ));    

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

int * arr = malloc ( 2 * sizeof ( int )); arr [ 0 ] = 1 ; arr [ 1 ] = 2 ; arr = realloc ( arr , 3 * sizeof ( int )); arr [ 2 ] = 3 ;                

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

Тип безопасности

mallocвозвращает указатель void ( void *), который указывает, что это указатель на область неизвестного типа данных. Использование приведения требуется в C++ из-за строгой системы типов, тогда как в C это не так. Можно «привести» (см. преобразование типов ) этот указатель к определенному типу:

int * ptr , * ptr2 ; ptr = malloc ( 10 * sizeof ( * ptr )); /* без приведения */ ptr2 = ( int * ) malloc ( 10 * sizeof ( * ptr )); /* с приведением */             

Выполнение такого гипсования имеет свои преимущества и недостатки.

Преимущества литья

Недостатки литья

Распространенные ошибки

Неправильное использование динамического распределения памяти часто может быть источником ошибок. К ним относятся ошибки безопасности или сбои программ, чаще всего из-за ошибок сегментации .

Наиболее распространенные ошибки следующие: [15]

Не проверяет наличие сбоев распределения
Выделение памяти не гарантируется и может вместо этого вернуть нулевой указатель. Использование возвращаемого значения без проверки того, успешно ли выделено, вызывает неопределенное поведение . Обычно это приводит к сбою (из-за возникающего в результате сбоя сегментации при разыменовывании нулевого указателя), но нет никакой гарантии, что сбой произойдет, поэтому полагаясь на это, можно также столкнуться с проблемами.
Утечки памяти
Неспособность освободить память с помощью freeприводит к накоплению неиспользуемой памяти, которая больше не используется программой. Это тратит ресурсы памяти впустую и может привести к сбоям выделения, когда эти ресурсы исчерпаны.
Логические ошибки
Все выделения должны следовать одному и тому же шаблону: выделение с помощью malloc, использование для хранения данных, освобождение с помощью free. Несоблюдение этого шаблона, например, использование памяти после вызова free( висячий указатель ) или перед вызовом malloc( дикий указатель ), вызов freeдважды («двойное освобождение») и т. д., обычно приводит к ошибке сегментации и приводит к сбою программы. Эти ошибки могут быть временными и сложными для отладки — например, освобожденная память обычно не сразу возвращается ОС, и, таким образом, висячие указатели могут сохраняться некоторое время и казаться работающими.

Кроме того, как интерфейс, предшествующий стандартизации ANSI C, mallocи его связанные функции имеют поведение, которое было намеренно оставлено на усмотрение реализации для определения его самой. Одним из них является выделение нулевой длины, что является большей проблемой, reallocпоскольку чаще всего размер изменяется до нуля. [16] Хотя и POSIX , и Single Unix Specification требуют надлежащей обработки выделений нулевого размера либо путем возврата NULL, либо чего-то другого, что может быть безопасно освобождено, [17] не все платформы обязаны соблюдать эти правила. Среди множества ошибок двойного освобождения, к которым это привело, особенно заметной была ошибка WhatsApp RCE 2019 года. [18] Способ обернуть эти функции, чтобы сделать их более безопасными, — просто проверить выделения памяти размером 0 и превратить их в выделения размером 1. (Возврат NULLимеет свои собственные проблемы: в противном случае он указывает на ошибку нехватки памяти. В случае reallocэто означало бы, что исходная память не была перемещена и освобождена, что опять же не относится к размеру 0, что приводит к двойному освобождению.) [19]

Реализации

Реализация управления памятью во многом зависит от операционной системы и архитектуры. Некоторые операционные системы предоставляют распределитель для malloc, в то время как другие предоставляют функции для управления определенными областями данных. Один и тот же динамический распределитель памяти часто используется для реализации как mallocи оператора newв C++ . [20]

На основе кучи

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

Метод кучи имеет несколько неотъемлемых недостатков:

dlmalloc и ptmalloc

Дуг Ли разработал общедоступный dlmalloc («Malloc Дуга Ли») как универсальный распределитель, начиная с 1987 года. Библиотека GNU C (glibc) является производной от ptmalloc Вольфрама Глогера («pthreads malloc»), ответвления dlmalloc с улучшениями, связанными с потоками. [21] [22] [23] По состоянию на ноябрь 2023 года последней версией dlmalloc является версия 2.8.6 от августа 2012 года. [24]

dlmalloc — это граничный тег-аллокатор. Память в куче выделяется в виде «кусков», 8-байтовой выровненной структуры данных , которая содержит заголовок и полезную память. Выделенная память содержит 8- или 16-байтовые накладные расходы для размера куска и флагов использования (аналогично dope-вектору ). Невыделенные куски также хранят указатели на другие свободные куски в области полезного пространства, делая минимальный размер куска 16 байт в 32-битных системах и 24/32 (в зависимости от выравнивания) байта в 64-битных системах. [22] [24] : 2.8.6, Минимальный выделенный размер 

Нераспределенная память группируется в « корзины » схожих размеров, реализуемые с помощью двухсвязного списка фрагментов (с указателями, хранящимися в нераспределенном пространстве внутри фрагмента). Корзины сортируются по размеру на три класса: [22] [24] : Наложенные структуры данных 

Разработчик игр Эдриан Стоун утверждает, что dlmalloc, как распределитель граничных тегов, недружелюбен к консольным системам, которые имеют виртуальную память, но не имеют подкачки по требованию . Это связано с тем, что его обратные вызовы сокращения и роста пула ( sysmalloc/ systrim) не могут использоваться для выделения и фиксации отдельных страниц виртуальной памяти. При отсутствии подкачки по требованию фрагментация становится более серьезной проблемой. [27]

Jemalloc для FreeBSD и NetBSD

Начиная с FreeBSD 7.0 и NetBSD 5.0, старая mallocреализация ( phkmallocот Poul-Henning Kamp ) была заменена на jemalloc, написанную Jason Evans. Основной причиной этого была недостаточная масштабируемость phkmallocс точки зрения многопоточности. Чтобы избежать конфликта блокировок, jemallocиспользует отдельные «арены» для каждого CPU . Эксперименты по измерению количества выделений в секунду в многопоточном приложении показали, что это делает его масштабируемым линейно с количеством потоков, в то время как для phkmalloc и dlmalloc производительность была обратно пропорциональна количеству потоков. [28]

Функция malloc в OpenBSD

Реализация функции OpenBSDmalloc использует mmap . Для запросов, размер которых превышает одну страницу, все выделение извлекается с помощью mmap; меньшие размеры назначаются из пулов памяти, поддерживаемых в mallocпределах ряда «страниц корзины», также выделенных с помощью mmap. [29] [ требуется лучший источник ] При вызове freeпамять освобождается и отменяет отображение из адресного пространства процесса с помощью munmap. Эта система разработана для повышения безопасности за счет использования преимуществ рандомизации макета адресного пространства и функций gap page, реализованных как часть mmap системного вызова OpenBSD , а также для обнаружения ошибок использования после освобождения — поскольку большое выделение памяти полностью отменяется после освобождения, дальнейшее использование вызывает ошибку сегментации и завершение программы.

Проект GrapheneOS изначально начинался с переноса распределителя памяти OpenBSD в библиотеку Bionic C Android. [30]

Копить malloc

Hoard — это распределитель, целью которого является масштабируемая производительность выделения памяти. Как и распределитель OpenBSD, Hoard использует mmapисключительно, но управляет памятью в кусках по 64 килобайта, называемых суперблоками. Куча Hoard логически разделена на одну глобальную кучу и несколько куч на процессор. Кроме того, существует локальный кэш потока, который может содержать ограниченное количество суперблоков. Выделяя только из суперблоков в локальной куче на поток или на процессор и перемещая в основном пустые суперблоки в глобальную кучу, чтобы их могли повторно использовать другие процессоры, Hoard сохраняет низкую фрагментацию, достигая при этом почти линейной масштабируемости с числом потоков. [31]

мималлок

Компактный распределитель памяти общего назначения с открытым исходным кодом от Microsoft Research , ориентированный на производительность. [32] Библиотека содержит около 11 000 строк кода .

Кэширование потоков malloc (tcmalloc)

Каждый поток имеет локальное хранилище потока для небольших выделений. Для больших выделений можно использовать mmap или sbrk . TCMalloc, malloc, разработанный Google, [33] имеет сборку мусора для локального хранения мертвых потоков. Считается, что TCMalloc более чем в два раза быстрее ptmalloc из glibc для многопоточных программ. [34] [35]

В ядре

Ядрам операционных систем необходимо выделять память так же, как это делают прикладные программы. Однако реализация mallocв ядре часто существенно отличается от реализаций, используемых библиотеками C. Например, буферы памяти могут нуждаться в соответствии со специальными ограничениями, налагаемыми DMA , или функция выделения памяти может вызываться из контекста прерывания. [36] Это требует mallocреализации, тесно интегрированной с подсистемой виртуальной памяти ядра операционной системы.

Переопределение malloc

Поскольку mallocи его родственники могут оказывать сильное влияние на производительность программы, не редкость переопределять функции для конкретного приложения с помощью пользовательских реализаций, оптимизированных для шаблонов распределения приложения. Стандарт C не предоставляет способа сделать это, но операционные системы нашли различные способы сделать это, используя динамическое связывание. Один из способов — просто связать другую библиотеку, чтобы переопределить символы. Другой, используемый в Unix System V.3 , — создать указатели функций mallocи free, которые приложение может сбросить до пользовательских функций. [37]

Наиболее распространенной формой в системах типа POSIX является установка переменной среды LD_PRELOAD с путем к распределителю, чтобы динамический компоновщик использовал эту версию malloc/calloc/free вместо реализации libc.

Ограничения по размеру распределения

Максимально возможный блок памяти, mallocкоторый можно выделить, зависит от хост-системы, в частности от размера физической памяти и реализации операционной системы.

Теоретически, наибольшее число должно быть максимальным значением, которое может храниться в типе size_t, который является беззнаковым целым числом, зависящим от реализации и представляющим размер области памяти. В стандарте C99 и более поздних версиях оно доступно как SIZE_MAXконстанта из . Хотя это и не гарантируется ISO C , обычно оно равно .<stdint.h>2^(CHAR_BIT * sizeof(size_t)) - 1

В системах glibc максимально возможный блок памяти, mallocкоторый можно выделить, составляет всего лишь половину этого размера, а именно . [38]2^(CHAR_BIT * sizeof(ptrdiff_t) - 1) - 1

Расширения и альтернативы

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

Смотрите также

Ссылки

  1. ^ ab 7.20.3 Функции управления памятью (PDF) . Спецификация ISO/IEC 9899:1999 (Технический отчет). стр. 313.
  2. ^ Саммит, Стив. "Глава 11: Распределение памяти". Заметки по программированию на языке C. Получено 11 июля 2020 г.
  3. ^ "aligned_alloc(3) - страница руководства Linux".
  4. ^ Страуструп, Бьярне (2008). Программирование: принципы и практика использования C++ . Эддисон Уэсли. стр. 1009. ISBN 978-0-321-54372-1.
  5. ^ "gcc manual". gnu.org . Получено 2008-12-14 .
  6. ^ Брайан В. Керниган, Деннис М. Ритчи, Язык программирования C , Prentice-Hall, 1978; Раздел 7.9 (стр. 156) описывает callocи cfree, а Раздел 8.7 (стр. 173) описывает реализацию для allocи free.
  7. ^ alloc(3)  –  Руководство программиста Unix версии 6
  8. ^ malloc(3)  –  Руководство программиста Unix версии 7
  9. Аноним, Руководство программиста Unix, том 1 , Холт Райнхарт и Уинстон, 1983 (авторские права принадлежат Bell Telephone Laboratories, 1983, 1979); manСтраница для mallocи т. д. указана на странице 275.
  10. ^ alloca(3)  –  Руководство по функциям библиотеки FreeBSD
  11. ^ calloc(3)  –  Руководство программиста Linux – Библиотечные функции
  12. ^ ab "Casting malloc". Cprogramming.com . Получено 2007-03-09 .
  13. ^ "clang: lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp Исходный файл". clang.llvm.org . Получено 2018-04-01 .
  14. ^ "Список часто задаваемых вопросов по comp.lang.c · Вопрос 7.7b". C-FAQ . Получено 2007-03-09 .
  15. ^ Reek, Kenneth (1997-08-04). Указатели на C (1-е изд.). Pearson. ISBN 9780673999863.
  16. ^ "MEM04-C. Остерегайтесь выделений нулевой длины - Стандарт кодирования SEI CERT C - Confluence". wiki.sei.cmu.edu .
  17. ^ "POSIX.1-2017: malloc". pubs.opengroup.org . Получено 29.11.2019 .
  18. ^ Awakened (2019-10-02). «Как ошибка двойного освобождения в WhatsApp превращается в RCE» . Получено 2019-11-29 .
  19. ^ Фелкер, Рич [@RichFelker] (2019-10-03). "Ого. WhatsApp RCE был неправильным поведением для realloc(p,0), на котором настаивают многие реализации" ( Твит ) . Получено 2022-08-06 – через Twitter .
  20. ^ Александреску, Андрей (2001). Современный дизайн C++: применение шаблонов обобщенного программирования и проектирования . Addison-Wesley. стр. 78.
  21. ^ "Домашняя страница malloc Вольфрама Глогера". malloc.de . Получено 01.04.2018 .
  22. ^ abc Kaempf, Michel (2001). "Vudo malloc tricks". Phrack (57): 8. Архивировано из оригинала 2009-01-22 . Получено 2009-04-29 .
  23. ^ "Glibc: Malloc Internals". sourceware.org Trac . Получено 2019-12-01 .
  24. ^ abc Ли, Дуг. "Распределитель памяти" . Получено 01.12.2019 .HTTP для исходного кода
  25. ^ "Настраиваемые параметры Malloc". GNU . Получено 2009-05-02 .
  26. ^ Сандерсон, Брюс (12.12.2004). «ОЗУ, виртуальная память, файл подкачки и все такое». Справка и поддержка Microsoft.
  27. ^ Стоун, Адриан. «Дыра, которую dlmalloc не может заполнить». Game Angst . Получено 01.12.2019 .
  28. ^ Эванс, Джейсон (16.04.2006). "Масштабируемая параллельная реализация malloc(3) для FreeBSD" (PDF) . Получено 18.03.2012 .
  29. ^ "libc/stdlib/malloc.c". Перекрестный справочник BSD, OpenBSD src/lib/ .
  30. ^ "История | GrapheneOS". grapheneos.org . Получено 2023-03-02 .
  31. ^ Бергер, Э.Д.; МакКинли, К.С.; Блюмофе, Р.Д.; Уилсон, П.Р. (ноябрь 2000 г.). Клад: масштабируемый распределитель памяти для многопоточных приложений (PDF) . ASPLOS -IX. Труды девятой международной конференции по архитектурной поддержке языков программирования и операционных систем . стр. 117–128. CiteSeerX 10.1.1.1.4174 . doi :10.1145/378993.379232. ISBN  1-58113-317-0.
  32. ^ Microsoft выпускает оптимизированный malloc() с открытым исходным кодом - Slashdot
  33. ^ Домашняя страница TCMalloc
  34. ^ Ghemawat, Sanjay; Menage, Paul; TCMalloc: Кэширование потоков Malloc
  35. ^ Каллаган, Марк (18.01.2009). "Высокая доступность MySQL: двойная пропускная способность sysbench с TCMalloc". Mysqlha.blogspot.com . Получено 18.09.2011 .
  36. ^ "kmalloc()/kfree() include/linux/slab.h". People.netfilter.org . Получено 2011-09-18 .
  37. ^ Левин, Джон Р. (2000) [октябрь 1999]. "Глава 9: Общие библиотеки". Линкеры и загрузчики. Серия Моргана Кауфмана по программной инженерии и программированию (1-е изд.). Сан-Франциско, США: Morgan Kaufmann . ISBN 1-55860-496-0. OCLC  42413382. Архивировано из оригинала 2012-12-05 . Получено 2020-01-12 .Код: [1][2] Опечатки: [3]
  38. ^ "malloc: заставить malloc завершаться сбоем с запросами, размер которых превышает PTRDIFF_MAX". Sourceware Bugzilla . 2019-04-18 . Получено 2020-07-30 .
  39. ^ "Почему использование alloca() не считается хорошей практикой?". stackoverflow.com . Получено 05.01.2016 .
  40. ^ Амарасингхе, Саман; Лейзерсон, Чарльз (2010). "6.172 Performance Engineering of Software Systems, Lecture 10". MIT OpenCourseWare . Массачусетский технологический институт. Архивировано из оригинала 22-06-2015 . Получено 27-01-2015 .
  41. ^ "alloca(3) - страница руководства Linux". man7.org . Получено 2016-01-05 .
  42. ^ posix_memalign  – Справочник по системным интерфейсам, Единая спецификация UNIX , версия 4 от The Open Group

Внешние ссылки