realloc() без проблем с присваиванием

Один из моих одноклассников прислал мне код и спросил, что с ним не так. Это было примерно так:

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

int main()
{
    int *d_array, number, divisor_count, i, size = 1;
    char answer;

    d_array = (int*) malloc(size * sizeof(int));

    do
    {
        printf("\nEnter a number:  ");
        scanf("%d", &number);
        divisor_count = 0;
        for(i = 2; i < number; i++)
            if (number % i == 0) divisor_count++;
        if(divisor_count == 0)
        {
            realloc(d_array,(size + 1) * sizeof(int));
            d_array[size - 1] = number;
            size++;
        }
        printf("\nIs there another number? y/n ");
        getchar();
        answer = getchar();
    } while (answer == 'y');

    for(i = 0; i < size - 1; i++)
        printf("\n%d", d_array[i]);

    return 0;
} 

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

Enter a number:  3
Is there another number? y/n y
Enter a number:  5
Is there another number? y/n y
Enter a number:  8
Is there another number? y/n y
Enter a number:  7
Is there another number? y/n y
Enter a number:  2
Is there another number? y/n n
4072680
5
7
2

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

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


person hattenn    schedule 05.01.2011    source источник
comment
Вызов неопределенного поведения — как, несомненно, делает ваш коллега — приводит к тому, что все становится возможным. То, что вы видели, совершенно верно, если вы вызываете неопределенное поведение; на самом деле нет особого смысла обсуждать это дальше. GIGO - мусор на входе, мусор на выходе. Вам не повезло, что что-то сработало (и да, я имею в виду НЕ-повезло!).   -  person Jonathan Leffler    schedule 05.01.2011


Ответы (7)


Это покажет вам немного о том, что происходит:

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

void dbg_print_array(unsigned sz, const int * array, const char * label) {
     fprintf(stderr, "{%s:\t%p:\t", label, array);
     while (sz--) {
         fprintf(stderr, " %x ", *array++);
     }
     fprintf(stderr, "}\n");
}

int main()
{
    int *d_array, number, divisor_count, i, size = 1;
    char answer;

    d_array = (int*) malloc(size * sizeof(int));
    dbg_print_array(size, d_array, "Initial");

    do
    {
        printf("\nEnter a number:  ");
        scanf("%d", &number);
        divisor_count = 0;
        for(i = 2; i < number; i++)
            if (number % i == 0) divisor_count++;
        if(divisor_count == 0)
        {
            int * p;
            dbg_print_array(d_array, size, "pre-realloc");
            p = realloc(d_array,(size + 1) * sizeof(int));
            dbg_print_array(d_array, size+1, "post-realloc (d_array)");
            dbg_print_array(p, size+1, "post-realloc (p)");
            d_array[size - 1] = number;
            size++;
        }
        printf("\nIs there another number? y/n ");
        getchar();
        answer = getchar();
    } while (answer == 'y');

    for(i = 0; i < size - 1; i++)
        printf("\n%d", d_array[i]);

    return 0;
} 

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

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

редактировать

Изучив вывод приведенного выше кода, вы сможете определить, когда realloc действительно возвращает указатель, отличный от того, который был передан (я предполагаю, что в вашем примере нужно освободить место для последнего прочитанного целого числа, потому что malloc, вероятно, округляется до 16 байтов для первого выделения - также потому, что realloc не abort, вероятно, потому что ему никогда не передавался неверный указатель).

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

person nategoose    schedule 05.01.2011
comment
Интересно, что код выглядит так, как будто он отлично работает, когда он скомпилирован с использованием GCC в Ubuntu. Если бы я сначала скомпилировал этот код в Ubuntu, я бы не научился правильно использовать realloc(). Как мы использовали его без назначения в нашем классе. Спасибо за всю помощь, я очень ценю это. - person hattenn; 07.01.2011

Всегда делайте это:

void* new_ptr = realloc(ptr, new_size);
if(!new_ptr) error("Out of memory");
ptr = new_ptr;

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

Кроме того, если realloc() возвращает NULL, это означает, что она не удалась. В этом случае ваша память, на которую указывает ptr, должна быть в какой-то момент освобождена, иначе у вас будет утечка памяти. Другими словами, никогда не делайте этого:

ptr = realloc(ptr, new_size);
person Jörgen Sigvardsson    schedule 05.01.2011
comment
Спасибо за ответ, но я не прошу правильный код, я просто спрашиваю, почему он выводит это в коде, написанном выше. - person hattenn; 05.01.2011
comment
Вероятно, потому, что процедуры управления памятью помечали код каким-то магическим числом, чтобы облегчить отладку. Есть ли различия с включенной отладкой или без нее? С Visual C++ вы получаете совершенно различное поведение подпрограмм управления памятью в зависимости от настроек режима отладки. - person Jörgen Sigvardsson; 05.01.2011

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

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

person James Greenhalgh    schedule 05.01.2011
comment
На самом деле это будет внутренняя библиотека распределения кучи, которая может быть частью ОС (вряд ли на настольных компьютерах), но, вероятно, является частью библиотеки, которая идет с компилятором (хотя и не обязательно). - person nategoose; 06.01.2011

Проблема в том, что после realloc память, на которую указывает d_array, считается свободной, поэтому код фактически пишет в свободную память. Похоже, что тем временем память выделяется для чего-то другого (используется scanf?), поэтому ее начало перезаписывается. Конечно, это совершенно не определено: любая часть освобожденной памяти может быть перезаписана в любой момент.

person Vlad    schedule 05.01.2011
comment
Ну, я вижу, что это поведение undefined, но вывод всегда один и тот же. Мне просто интересно, меняет ли realloc() содержимое памяти при перемещении в другое место. - person hattenn; 05.01.2011
comment
@evothur: да, это меняет содержимое (если новый размер больше старого). См.: cplusplus.com/reference/clibrary/cstdlib/realloc Кстати, эта ссылка, возможно, является прототипом кода вашего друга. :-) - person Vlad; 05.01.2011
comment
@evothur: цитата из предыдущей ссылки: если новый размер больше, значение вновь выделенной части неопределенно. - person Vlad; 05.01.2011

Поскольку вас больше всего интересует, почему вы получаете повторяющийся (даже если неверный) вывод из неопределенного поведения, вот один сценарий, который может привести к тому, что вы видите. Я думаю, что понимание механизмов, лежащих в основе неопределенного поведения, может быть ценным — просто имейте в виду, что это гипотетически, и на самом деле это может быть не то, почему вы видите то, что видите. Для неопределенного поведения результаты, которые вы видите, могут меняться от одной компиляции к следующей или от одного запуска к следующему (для странного случая, когда простое изменение имени переменной в программе с неопределенным поведением изменило вывод программы, см. < a href="https://stackoverflow.com/questions/4575697/unexpected-output-from-bubblesort-program-with-msvc-vs-tcc/4577565#4577565">Неожиданный вывод программы Bubblesort с MSVC vs TCC).

  1. d_array инициализируется вызовом malloc(size * sizeof(int)) перед входом в цикл do/while. Указатель d_array никогда не изменяется после этой точки (хотя он может больше не указывать на выделенную память, как мы увидим позже).

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

  3. При вводе 5 выполняется еще один вызов realloc(). На этот раз библиотека решает, что ей нужно выделить другой блок. Так и происходит (после копирования содержимого того, на что указывает d_array). Часть этого перераспределения приводит к тому, что блок d_array указывает на освобождение, и бухгалтерия библиотеки перезаписывает 3, который был в этом блоке, на 4072680. Может быть, указатель на что-то, о чем заботится библиотека — кто знает. Главное, что блок теперь снова принадлежит библиотеке, и она (не вы) может делать с ним, что хочет.

  4. Теперь 5 записывается в d_array + 1 (что недопустимо, так как блок, на который указывает d_array, был освобожден). Итак, d_array[0] == 4072680 и d_array[1] == 5.

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

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

person Michael Burr    schedule 05.01.2011
comment
Спасибо за все объяснения. Это действительно очень помогло мне, я очень ценю это. - person hattenn; 07.01.2011

Попробуй это :

d_array = realloc(d_array,(size + 1) * sizeof(int));

Сделал это и отлично работал на моем компьютере.

Если вы используете gcc, gcc -Wall -o даст вам предупреждение, которое вы искали.

вы realloc(), но вы не используете новую выделенную память. Проще говоря. Он по-прежнему указывает на старый (в зависимости от того, использовал ли realloc() тот же блок или переместил его в другое место). Если вам «повезло» и использовался тот же блок, вы просто продолжаете запись, иначе вы записываете в старое место и в конечном итоге записываете в место, где не должны (потому что realloc() free() — это старый блок внутри. realloc() не не изменяйте свою переменную-указатель, просто перераспределяет пространство. вам нужно изменить адрес памяти с результатом realloc(). И, как сказано в комментариях, вам нужно проверить успешность realloc()ation. новое выделенное пространство содержит данные старого буфера (пока выделенная новая память больше старой) содержимое старой памяти не изменяется, но освобождается старое место.

person Community    schedule 05.01.2011
comment
И когда realloc возвращает NULL, вы окончательно облажались, используя такое. - person user562374; 05.01.2011
comment
Это включено в мой вопрос, я знаю, что это правильный путь, но я не пытаюсь исправить код. Вопрос в том, почему этот неправильный код выдает такой вывод, какая логика стоит за этим? - person hattenn; 05.01.2011
comment
Ну, я проверил это, и вообще realloc() возвращает пуант в другое место памяти, но вывод всегда один и тот же, первый не правильный, а остальные. - person hattenn; 05.01.2011

Я думаю, вам нужно использовать realloc следующим образом:

d_array = realloc(d_array,(size + 1) * sizeof(int));

и не только:

realloc(d_array,(size + 1) * sizeof(int));

Еще одна проблема, которую я вижу, заключается в том, что изначально size=1, поэтому при первом запуске кода он делает это:

realloc(d_array,(size + 1) * sizeof(int));

размер + 1 = 2 (выделение памяти для 2 целых чисел, но вам нужен только один.) Решением может быть начальный размер в 0.

person lezo    schedule 05.01.2011
comment
Спасибо за ответ, но я не пытаюсь исправить код, я просто пытаюсь понять поведение realloc() - person hattenn; 05.01.2011