Может ли realloc fail (возвращать NULL)при обрезке?

Если сделать следующие:

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

и их я использую realloc:

array = realloc(array, 5 * sizeof(int));

на второй строке (и только она), может ли он вернуться NULL?

7 ответов


да, может. нет никаких гарантий реализации на realloc(), и он может возвращать другой указатель даже при сжатии.

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


или он может просто решить, что лучше, чтобы переместить блок

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

#include <stdlib.h>                                                          
#include <stdio.h>                                                           

int main()                                                                   
{                                                                            
    int n;                                                                   

    for (n = 0; n <= 10; ++n)                                                
    {                                                                        
        void* array = malloc(n * sizeof(int));                               
        size_t* a2 = (size_t*) array;                                        

        printf("%d -> %zu\n", n, a2[-1]);                                    
    }                                                                        
}

и для n

Итак, если он сжался int[10] до int[5], выделенный размер уменьшился бы от 48 до 32, эффективно давая 16 свободных байтов. Поскольку (как только что было отмечено) он не будет выделять ничего меньше чем 32 байта, эти 16 байтов теряются.

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


самая уместная цитата из стандарта C99 (7.20.3.4 в realloc функции):

возвращает

4 к realloc функция возвращает указатель на новый объект (который мая имеют то же значение, что и указатель на старый объект) или нулевой указатель, если новый объект не может быть выделен.

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


кстати, я думаю, ты мог бы считают realloc() несколько не одобрять. Если вы посмотрите на C++, новые интерфейсы выделения памяти (new / delete и распределители) даже не поддерживают такую вещь. Они всегда ожидают, что вы выделите новый блок. Но это всего лишь свободный комментарий.


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

void *safe_trim(void *p, size_t n) {
    void *p2 = realloc(p, n);
    return p2 ? p2 : p;
}

и возвращаемое значение всегда будет указывать на объект в размере n.

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


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

реализация может решить реализовать realloc самым "примитивным" способом: делая безусловное malloc для нового блока памяти скопируйте данные и free-ing старый блок. Очевидно, что такая реализация может потерпеть неудачу в ситуациях с низкой памятью.


Не рассчитывайте на это. В стандарте нет такого положения; в нем просто говорится:"или нулевой указатель, если новый объект не может быть выделено".

вам будет трудно найти такую реализацию, но в соответствии со стандартом она все равно будет совместимой.


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

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

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


realloc не будет сбоев в сжатии существующей памяти, поэтому она не вернется NULL. Он может вернуться NULL только в случае сбоя во время расширения.

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

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


немного поздно, но есть по крайней мере одна популярная реализация, которая realloc() с размером smaler может произойти сбой: TCMalloc. (По крайней мере, насколько я понимаю, кодекс)

Если Вы читаете файл tcmalloc.cc функции do_realloc_with_callback(), вы увидите, что если вы сжимаете достаточно (50% выделенной памяти, иначе она будет проигнорирована), TCMalloc сначала выделит новую память (и возможно сбой), а затем скопируйте ее и удалите старую память.

Я не копирую исходный код, потому что я не уверен, что авторские права (TCMalloc и Stackoverflow) позволят это, но вот ссылка на источник