Использует ли malloc () один и тот же адрес памяти в цикле? - PullRequest
2 голосов
/ 30 октября 2019

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

В цикле, подобном следующему:

// will only know `xlen` at runtime so I guess I have to use malloc here
char *x = malloc((xlen + 1) * sizeof(*x));
assert(x != NULL);  // memory allocation failed

while (fgets(line, sizeof(line), fp) != NULL) {
    strncpy(x, line, xlen);  // copy first xlen characters of line
    x[xlen] = '\0';          // ensure null terminating string

    // do something with x
    // x can now be "reused" for another line/loop iteration

    // free(x) - errors out with "pointer being freed was not allocated"
}
fclose(fp)
free(x);

Если внутри цикла вызывается оператор free(x), то при запуске этой программы появляется сообщение об ошибке, похожее на a.out(37575,0x7fff964ce3c0) malloc: *** error for object 0x7fab47c02630: pointer being freed was not allocated.

Почему я вижу это сообщение об ошибке и что его вызывает?

Будет ли блок адресов памяти x повторно использоваться на каждой итерации цикла? (Я бы сказал, да, и это на самом деле было бы тем, что я хотел в этом случае) В этом случае безопасно ли только освободить выделенную память x вне области действия цикла?

Ответы [ 4 ]

3 голосов
/ 30 октября 2019

free(x); освобождает память, выделенную char *x = malloc(.... Это освобождает всю память, и вам не нужно беспокоиться о том, сколько памяти было, поскольку она отслеживает это. Вам просто нужно позвонить free(x); один раз , как вы правильно делаете. Вот почему вы получите ошибку, если освободите ее внутри цикла.

Означает ли это, что адресный блок памяти x будет «повторно использоваться» на каждой итерации? (Я бы сказал, да, и это на самом деле было бы то, что я хотел в этом случае)

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

В этом случае безопасно ли освобождать только выделенную память x вне области цикла?

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

2 голосов
/ 30 октября 2019

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

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

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

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

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

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

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

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

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

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

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

1 голос
/ 30 октября 2019

Вы можете продолжить повторное использование памяти (например, buf, созданной char *buf = calloc(sizeOfBuf);), выделенной вам при одном вызове calloc(), до тех пор, пока не будет вызван free(buf), как если бы buf создавался статически, например, char buf[sizeOfBuf]; = 0. Однако , если размер буфера, первоначально созданного с помощью calloc(), должен измениться , realloc () , доступен для этого и является предпочтительнымметод. Однако есть некоторые предостережения относительно использования realloc. Вот пример использования realloc, упакованного в функцию, которая изменяет размер существующего динамически распределенного буфера и заботится о некоторых предостережениях:

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

// initial call to calloc
char *buf = calloc(sizeOfBuf);
if(buf)
{
    while(someConditionIsTrue)
    {
        // read new content
        //...
        //new content needs to be added to buf
        char *tmp = realloc(buf, newSizeOfBuffer);
        if(!tmp)//if failed...
        {
            free(buf);//... free original buf to prevent memory loss
            return NULL;// return null, caller must test for this
        }
        buf = tmp;//...else copy new mem location back to original pointer
        //continue doing stuff with buf
    }

    //when done with buf, free it
    free(buf);
}

Еще одно предположение, рассмотрим calloc(), которое возвращает неинициализированную память, т. Е. У вас есть пространство, которое содержит то, что занимало его, когда оно было вам дано. Рекомендуется следовать этой команде с методом очистки буфера:

memset(buf, 0, sizeOfBuf);

Или используйте calloc () .

1 голос
/ 30 октября 2019

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

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