Поиск…


Вступление

Для управления динамически распределенной памятью стандартная библиотека C предоставляет функции malloc() , calloc() , realloc() и free() . В C99 и более поздних версиях также есть aligned_alloc() . Некоторые системы также предоставляют alloca() .

Синтаксис

  • void * aligned_alloc (size_t alignment, size_t size); / * Только с C11 * /
  • void * calloc (size_t nelements, size_t size);
  • void free (void * ptr);
  • void * malloc (size_t size);
  • void * realloc (void * ptr, size_t size);
  • void * alloca (size_t size); / * от alloca.h, не стандартный, не портативный, опасный. * /

параметры

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

замечания

C11

Обратите внимание: aligned_alloc() определен только для C11 или более поздних aligned_alloc() .

Системы, такие как основанные на POSIX, предоставляют другие способы выделения выровненной памяти (например, posix_memalign() ), а также другие параметры управления памятью (например, mmap() ).

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

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

int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL) 
{
    perror("malloc failed");
    return -1;
}

free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
   has undefined behavior, until a new value is stored into it. */

/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */

Память, на которую указывает p , восстанавливается (либо посредством реализации libc, либо базовой ОС) после вызова free() , поэтому доступ к этому свободному блоку памяти через p приведет к неопределенному поведению . Указатели, которые ссылаются на освобожденные элементы памяти, обычно называются оборванными указателями и представляют угрозу безопасности. Кроме того, стандарт C утверждает, что даже доступ к значению висячего указателя имеет неопределенное поведение. Обратите внимание, что сам указатель p может быть переназначен, как показано выше.

Обратите внимание, что вы можете звонить только free() по указателям, которые были возвращены непосредственно из функций malloc() , calloc() , realloc() и aligned_alloc() или где документация сообщает вам, что память была выделена таким образом (функции такие как strdup () являются заметными примерами). Освобождая указатель, который есть,

  • полученные с помощью оператора & на переменной, или
  • в середине выделенного блока,

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

Существуют две распространенные стратегии предотвращения таких случаев неопределенного поведения.

Первое и предпочтительное - просто: p перестает существовать, когда он больше не нужен, например:

if (something_is_needed())
{

    int *p = malloc(10 * sizeof *p);
    if (p == NULL) 
    {
        perror("malloc failed");
        return -1;
    }

    /* do whatever is needed with p */

    free(p);
}

Вызывая free() непосредственно перед концом содержащего блока (то есть } ), p сама перестает существовать. Компилятор даст ошибку компиляции при любой попытке использовать p после этого.

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

free(p);
p = NULL;     // you may also use 0 instead of NULL

Аргументы для этого подхода:

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

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

    Этот подход, следовательно, следует за неудачной концепцией .

  • Безопасно освобождать нулевой указатель. В стандарте C указано, что free(NULL) не действует:

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

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

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

Стандартное распределение

Функции распределения динамической памяти C определены в заголовке <stdlib.h> . Если вы хотите динамически выделять пространство памяти для объекта, можно использовать следующий код:

int *p = malloc(10 * sizeof *p);
if (p == NULL) 
{
    perror("malloc() failed");
    return -1;
}

Это вычисляет количество байтов, которое занимает десять int s в памяти, затем запрашивает, что много байтов из malloc и присваивает результат (то есть начальный адрес блока памяти, который только что был создан с помощью malloc ), указателю с именем p .

Рекомендуется использовать sizeof для вычисления объема запрашиваемой памяти, так как результат sizeof определяется реализацией (за исключением типов символов , которые являются char , signed char и unsigned char , для которых sizeof определен всегда, чтобы дать 1 ).

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

Память, динамически распределенная с помощью malloc() может быть изменена с использованием realloc() или, если она больше не понадобится, освобождается с помощью free() .

Альтернативно, объявляя int array[10]; будет выделять один и тот же объем памяти. Однако, если он объявлен внутри функции без ключевого слова static , он будет использоваться только внутри функции, в которой он объявлен, и функций, которые он вызывает (поскольку массив будет выделен в стеке, и пространство будет выпущено для повторного использования, когда функция возвращает). В качестве альтернативы, если она определена со static внутри функции или если она определена вне любой функции, то ее время жизни является временем жизни программы. Указатели также могут быть возвращены из функции, однако функция в C не может вернуть массив.

Нулевая память

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

int *p = calloc(10, sizeof *p);
if (p == NULL) 
{
    perror("calloc() failed");
    return -1;
}

Замечание о calloc : Большинство (обычно используемых) реализаций оптимизируют calloc() для производительности, поэтому он будет быстрее, чем вызов malloc() , затем memset() , хотя сетевой эффект идентичен.

Выровненная память

C11

C11 представила новую функцию aligned_alloc() которая выделяет пространство с заданным выравниванием. Он может использоваться, если выделенная память необходима для выравнивания на определенных границах, которые не могут быть удовлетворены malloc() или calloc() . Функции malloc() и calloc() выделяют память, которая соответствующим образом выровнена для любого типа объекта (т.е. выравнивание равно alignof(max_align_t) ). Но с aligned_alloc() можно запросить большее выравнивание.

/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
    perror("aligned_alloc()");
    return -1;
}
free(ptr);

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

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

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

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int *p = malloc(10 * sizeof *p);
    if (NULL == p) 
    {
        perror("malloc() failed");
        return EXIT_FAILURE;
    }
 
    p[0] = 42;
    p[9] = 15;

    /* Reallocate array to a larger size, storing the result into a
     * temporary pointer in case realloc() fails. */
    {
        int *temporary = realloc(p, 1000000 * sizeof *temporary);

        /* realloc() failed, the original allocation was not free'd yet. */
        if (NULL == temporary)
        {
            perror("realloc() failed");
            free(p); /* Clean up. */
            return EXIT_FAILURE;
        }      

        p = temporary;
    }

    /* From here on, array can be used with the new size it was 
     * realloc'ed to, until it is free'd. */

    /* The values of p[0] to p[9] are preserved, so this will print:
       42 15
    */
    printf("%d %d\n", p[0], p[9]);

    free(p);

    return EXIT_SUCCESS;
}

Перераспределенный объект может иметь или не иметь тот же адрес, что и *p . Поэтому важно зафиксировать возвращаемое значение из realloc которое содержит новый адрес, если вызов будет успешным.

Убедитесь, что возвращаемое значение realloc присваивается temporary а не оригиналу p . realloc вернет null в случае любого сбоя, который перезапишет указатель. Это потеряет ваши данные и создаст утечку памяти.

Многомерные массивы переменной величины

C99

Поскольку C99, C имеет массивы переменной длины, VLA, эта модель массивов с границами, которые известны только во время инициализации. Хотя вы должны быть осторожны, чтобы не выделять слишком большие VLA (они могут разбивать ваш стек), использование указателей в VLA и использование их в выражениях sizeof в порядке.

double sumAll(size_t n, size_t m, double A[n][m]) {
    double ret = 0.0;
    for (size_t i = 0; i < n; ++i)
       for (size_t j = 0; j < m; ++j)
          ret += A[i][j]
    return ret;
}

int main(int argc, char *argv[argc+1]) {
   size_t n = argc*10;
   size_t m = argc*8;
   double (*matrix)[m] = malloc(sizeof(double[n][m]));
   // initialize matrix somehow
   double res = sumAll(n, m, matrix);
   printf("result is %g\n", res);
   free(matrix);
}

Здесь matrix является указателем на элементы типа double[m] , а выражение sizeof с double[n][m] гарантирует, что оно содержит пространство для n таких элементов.

Все это пространство распределено смежно и, таким образом, может быть освобождено одним звонком на free .

Наличие VLA на языке также влияет на возможные объявления массивов и указателей в заголовках функций. Теперь внутри [] параметров массива допускается общее целочисленное выражение. Для обеих функций выражения в [] используют параметры, которые были объявлены ранее в списке параметров. Для sumAll это длины, которые пользовательский код ожидает для матрицы. Что касается всех параметров функции массива в C, то самое внутреннее измерение переписывается на тип указателя, поэтому это эквивалентно объявлению

  double sumAll(size_t n, size_t m, double (*A)[m]);

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

По существу, для main выражение argc+1 является минимальной длиной, которую стандарт C предписывает для аргумента argv .

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

realloc (ptr, 0) не эквивалентен свободному (ptr)

realloc концептуально эквивалентен malloc + memcpy + free на другом указателе.

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

Таким образом, realloc(ptr,0) не эквивалентен free(ptr) . Это может

  • быть «ленивой» реализацией и просто вернуть ptr
  • free(ptr) , выделите фиктивный элемент и верните его
  • free(ptr) и возврат 0
  • просто верните 0 для отказа и ничего не делайте.

Поэтому, в частности, последние два случая неразличимы по коду приложения.

Это означает, что realloc(ptr,0) может не освобождать / освобождать память и, следовательно, никогда не следует использовать в качестве замены free .

Пользовательское управление памятью

malloc() часто вызывает базовые функции операционной системы для получения страниц памяти. Но нет ничего особенного в функции, и она может быть реализована в прямом C, объявив большой статический массив и выделяя из него (есть небольшая трудность в обеспечении правильного выравнивания, на практике выравнивание до 8 байтов почти всегда адекватно).

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

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

/* typical control block */
struct block
{
   size_t size;         /* size of block */
   struct block *next;  /* next block in free list */ 
   struct block *prev;  /* back pointer to previous block in memory */
   void *padding;       /* need 16 bytes to make multiple of 8 */
}

static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;

Многим программам требуется большое количество распределений небольших объектов одинакового размера. Это очень легко реализовать. Просто используйте блок со следующим указателем. Поэтому, если требуется блок из 32 байтов:

union block
{
   union block * next;
   unsigned char payload[32];
}  

static union block arena[100];
static union block * head; 
void init(void)
{
    int i;
    for (i = 0; i < 100 - 1; i++)
        arena[i].next = &arena[i + 1];
    arena[i].next = 0; /* last one, null */
    head = &block[0];
}
 
void *block_alloc()
{
    void *answer = head;
    if (answer)
        head = head->next;
    return answer;
}

void block_free(void *ptr)
{
    union block *block = ptr;
    block->next = head;
    head - block;
}

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

alloca: выделить память в стеке

Предостережение: alloca упоминается здесь только для полноты. Он полностью не переносится (не подпадает под действие каких-либо общих стандартов) и имеет ряд потенциально опасных функций, которые делают его небезопасным для не подозревающих. Современный C-код должен заменить его массивами переменной длины (VLA).

Ручная страница

#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default

void foo(int size) {
    char *data = alloca(size);
    /*
      function body;
    */
    // data is automatically freed
}

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

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

По этой причине не рекомендуется использовать alloca внутри цикла или рекурсивную функцию.

И поскольку память free при возврате функции, вы не можете вернуть указатель как результат функции ( поведение будет неопределенным ).

Резюме

  • вызов идентичен malloc
  • автоматически free'd при возврате функции
  • несовместимые со free функциями realloc ( неопределенное поведение )
  • указатель не может быть возвращен как результат функции ( неопределенное поведение )
  • размер ограниченного пространства стека, который (на большинстве машин) намного меньше места кучи, доступного для использования malloc()
  • избегайте использования alloca() и VLA (массивы с переменной длиной) в одной функции
  • alloca() не так переносима, как malloc() et al.

Рекомендация

  • Не используйте alloca() в новом коде
C99

Современная альтернатива.

void foo(int size) {
    char data[size];
    /*
      function body;
    */
    // data is automatically freed
}

Это работает там, где alloca() делает и работает в местах, где alloca() не работает (например, внутри циклов). Он предполагает либо реализацию C99, либо реализацию C11, которая не определяет __STDC_NO_VLA__ .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow