realloc () без задачи присваивания - PullRequest
1 голос
/ 05 января 2011

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

#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 () изменяет содержимое старой памяти?

Ответы [ 7 ]

4 голосов
/ 05 января 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);
2 голосов
/ 05 января 2011

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

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

1 голос
/ 06 января 2011

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

#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 может принять решение на основе того, что оно прочитало, в результате чего кто знает, что. На этом этапе программы вы должны рассматривать каждое поведение как undefined , поскольку основные предположения, сделанные для нормальной работы, больше не действительны.

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

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

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

1 голос
/ 05 января 2011

Поскольку вам, кажется, больше всего интересно, почему вы получаете повторяемый (даже если неверный) вывод из неопределенного поведения, вот один сценарий, который может привести к тому, что вы видите. Я думаю, что понимание основных механизмов, лежащих в основе неопределенного поведения, может быть полезным - просто имейте в виду, что это гипотетически, и на самом деле это может быть не совсем то, почему вы видите то, что видите. Для неопределенного поведения результаты, которые вы видите, могут измениться от одной компиляции к следующей или от одного запуска до следующего (для странного случая, когда простое изменение имени переменной в программе с неопределенным поведением изменило выходные данные программы, см. Неожиданный вывод из программы Bubblesort с MSVC против 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() могли бы реально перераспределить.

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

1 голос
/ 05 января 2011

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

0 голосов
/ 05 января 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));

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

0 голосов
/ 05 января 2011

попробуйте:

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

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

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

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

...