realloc не может перераспределить ранее распределенный указатель

Я работаю над функцией чтения полной строки из stdin в char* с использованием getchar(), и в основном это работает, но когда я ввожу более длинную строку, я получаю

realloc(): неверный следующий размер: 0x00000000007ca010

Вот функция:

char *readCMDLine() {
    char *cmdline = malloc(sizeof(char));
    int counter = 0;
    char c;
    while (c != 10) {
        c = getchar();
        cmdline = realloc(cmdline, sizeof(cmdline) + sizeof(char));
        cmdline[counter] = c; 
        counter++;
    }
    return cmdline;
}

Есть ли способ исправить это, чтобы я мог читать с ним большие строки?


person Reaper9806    schedule 03.01.2016    source источник
comment
Посмотрите, что показывает sizeof(cmdline). Это может быть не то, что вы ожидаете. Также неразумно перераспределять после каждого символа.   -  person Sami Kuhmonen    schedule 03.01.2016
comment
@EdHeal: в этот момент строка не завершена.   -  person EOF    schedule 03.01.2016
comment
@EdHeal в этом сценарии невозможен...   -  person Sourav Ghosh    schedule 03.01.2016
comment
Справедливая точка выше sizeof(cmdline) -> counter + 1   -  person Ed Heal    schedule 03.01.2016
comment
Другие проблемы: 1) c должно быть int 2) Вместо использования чисел, таких как 10, используйте '\n' вместо этого 3) char c; while(c != 10) Будет ли условие истинным или ложным в первой итерации? 4) Вы не завершили буфер NUL.   -  person Spikatrix    schedule 03.01.2016
comment
Не удалось инициализировать c. Таким образом, программа может случайным образом ничего не возвращать, если c случайным образом инициализируется значением 10. Также не завершается строка с помощью '\0' и добавляется новая строка к возвращаемому значению. Это может быть как задумано, но очень нестандартная обработка строк.   -  person Persixty    schedule 03.01.2016
comment
Спасибо всем ответившим, теперь все работает   -  person Reaper9806    schedule 03.01.2016
comment
предложить заменить всю функцию на getline() с соответствующими параметрами. Не нужно заново изобретать велосипед.   -  person user3629249    schedule 04.01.2016
comment
sizeof(cmdline) - это размер указателя (обычно 4 или 8 байтов). Предлагается сохранить счетчик количества выделенных в данный момент байтов памяти и передать этот счетчик как часть второго параметра в realloc() всегда присваивать возвращаемое значение из realloc() временному указателю. , затем проверьте (!=NULL) этот временный указатель, чтобы убедиться, что операция прошла успешно, прежде чем назначать указатель cmdline. В противном случае при сбое realloc() текущий указатель на выделенную память будет потерян, что приведет к утечке памяти, а использование этого сбойного указателя приведет к событию сбоя сегмента.   -  person user3629249    schedule 04.01.2016


Ответы (4)


Есть несколько проблем с вашим кодом:

  • Вы тестируете while (c != 10), но c не инициализируется в первый раз, и вы не позволяете файлу закончиться до следующего '\n'. Вы также предполагаете ASCII, некоторые системы до сих пор используют EBCDIC, где '\n' не 10.
  • Вы перераспределяете, используя sizeof(cmdline) вместо counter. sizeof(cmdline) - это размер указателя, а не текущий размер выделенного массива.
  • Вы сохраняете getchar() в переменной char, EOF не является значением, которое может туда поместиться, и, возможно, значениями между 128 и UCHAR_MAX, если char подписано на вашей платформе. Используйте для этого int.
  • Вы не завершаете выделенную строку нулем. Ни malloc, ни realloc не инициализируют выделенную память.
  • Вы никогда не проверяете malloc или realloc сбой.
  • Вероятно, вам следует вернуть NULL в конце файла, иначе невозможно определить конец файла по последовательностям пустых строк.
  • Вы перераспределяете буфер для каждого символа, это неэффективно, но может быть оптимизировано на более позднем этапе, сначала исправьте код.

Вот исправленная версия:

#include <stdlib.h>

char *readCMDLine(void) {
    char *cmdline = malloc(1);
    size_t counter = 0, size = 1;
    int c;

    if (cmdline == NULL)
        return NULL;

    while ((c = getchar()) != EOF && c != '\n') {
        char *p = realloc(cmdline, counter + 2);
        if (p == NULL) {
            free(cmdline);
            return NULL;
        }
        cmdline = p;
        cmdline[counter++] = c;
    }
    cmdline[counter] = '\0';
    if (c == EOF && counter == 0) {
        free(cmdline);
        cmdline = NULL;
    }
    return cmdline;
}

Вот оптимизированная версия, которая гораздо реже вызывает realloc:

#include <stdlib.h>

#define ALLOCATE_INCREMENT  100
char *readCMDLine(void) {
    char *p, *cmdline = NULL;
    size_t counter = 0, size = 0;
    int c;

    while ((c = getchar()) != EOF && c != '\n') {
        if (counter > size - 2) {
            p = realloc(cmdline, size += ALLOCATE_INCREMENT);
            if (p == NULL) {
                free(cmdline);
                return NULL;
            }
            cmdline = p;
        }
        cmdline[counter++] = c;
    }
    if (counter == 0) {
        if (c == EOF || (cmdline = malloc(1)) == NULL)
            return NULL;
    } else {
        p = realloc(cmdline, counter + 1);
        if (p != NULL)
            cmdline = p;
    }
    cmdline[counter] = '\0';
    return cmdline;
} 
person chqrlie    schedule 03.01.2016
comment
Это по-прежнему страдает от нежелательного поведения «перераспределения для каждого символа». - person John Hascall; 03.01.2016
comment
@JohnHascall: Хорошо, я использую подход, аналогичный вашему, но он неоптимален: нужно использовать довольно большой автоматический массив и перераспределять буфер, когда этот локальный массив становится слишком маленьким, и для конечного результата. Это уменьшит количество перераспределений и, возможно, уменьшит фрагментацию. - person chqrlie; 03.01.2016

Использование sizeof(cmdline) не делает того, что вы ожидаете. При использовании с указателями он возвращает размер указателя, а не выделенную память. Это отличается для массивов, определенных во время компиляции.

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

Также одна вещь стиля: sizeof(char) всегда 1, поэтому это не нужно.

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

person Sami Kuhmonen    schedule 03.01.2016

Проблема здесь в том,

 sizeof(cmdline)

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

Теперь, чтобы добавить немного об ошибке,

realloc(): неверный следующий размер: 0x00000000007ca010

в основном то, что происходит,

cmdline = realloc(cmdline, sizeof(cmdline) + sizeof(char));

на самом деле не изменяет размер выделенной памяти, как вы могли бы подумать. Таким образом, когда значение counter достаточно велико, чтобы указать на лишнюю память в случае cmdline[counter], вы, по сути, вызываете неопределенное поведение. Таким образом, из-за UB (и последующего повреждения памяти) realloc() выдает ошибку и выдает ошибку сегментации в качестве побочного эффекта.

Кроме того, чтобы добавить

  • Пункт 1:

     pointer = realloc(pointer....)
    

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

  • Пункт 2:

    getchar() возвращает int, и все возвращаемые значения могут не соответствовать char (например, EOF ). Измените тип c на int.

  • Пункт 3:

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

  • Пункт 4:

    Вы должны проверить успешность malloc() и realloc() перед использованием возвращенного указателя.

person Sourav Ghosh    schedule 03.01.2016

Объединяем все предложения:

#define BYTES_TO_ADD 80     /* a rather arbitrary number */

char * readCMDLine ( void ) {
    char * cmdline = NULL;
    size_t cmdSize = 0;
    size_t cmdLen = 0;
    int    c;

    while ((c = getchar()) != EOF) {
        if (cmdLen == cmdSize) {
            char * tmp = realloc(cmdline, (cmdSize += BYTES_TO_ADD));  
            if (tmp == NULL) { free(cmdline); return NULL; } 
            cmdline = tmp;
        }
        cmdline[cmdLen++] = (c == '\n') ? '\0' : c;
        if (c == '\n') return cmdline;
    }
    return NULL;   /* no newline */
}
person John Hascall    schedule 03.01.2016
comment
Очевидно, что делать с неполной строкой — решать разработчику. Я предпочитаю считать это недопустимым вводом. Другие могут выбрать возврат частичного результата. - person John Hascall; 03.01.2016
comment
Как и печально известные gets() и нестандартные, но полезные getline() функции. Сделать другой выбор — это, конечно, дизайнерское решение, но неожиданное. - person chqrlie; 03.01.2016