Комбинация getline()/strsep() вызывает ошибку сегментации

Я получаю ошибку сегментации при запуске кода ниже.

В основном он должен читать файл .csv с более чем 3M строк и делать другие вещи (не относящиеся к проблеме), но после 207746 итераций он возвращает ошибку сегментации. Если я удалю p = strsep(&line,"|"); и просто напечатаю все line, он напечатает> 3M строк.

int ReadCSV (int argc, char *argv[]){

    char *line = NULL, *p;
    unsigned long count = 0;

    FILE *data;
    if (argc < 2) return 1;
    if((data = fopen(argv[1], "r")) == NULL){
        printf("the CSV file cannot be open");
        exit(0);
    }


    while (getline(&line, &len, data)>0) {

        p = strsep(&line,"|");  

        printf("Line number: %lu \t p: %s\n", count, p);
        count++;
    }

    free(line);
    fclose(data);

    return 0;
}

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


person Arduino    schedule 28.12.2017    source источник
comment
Есть ли в строке # 207746 хотя бы один разделитель? strsep() ведет себя иначе, если это не так.   -  person Aric TenEyck    schedule 28.12.2017
comment
strsep изменяет свой первый аргумент, так что вы теряете начало буфера getline, выделенного для вас первым. Не делайте этого, используйте отдельный временный файл, если вы должны использовать strsep. И проверьте, что p не NULL. А где заявлено len? Это ноль перед вызовом getline?   -  person Useless    schedule 28.12.2017


Ответы (2)


Сочетание getline и strsep часто вызывает путаницу, потому что обе функции изменяют указатель, который вы передаете им по указателю в качестве начального аргумента. Если вы снова передадите указатель, который прошел через strsep, в getline, вы рискуете получить неопределенное поведение на второй итерации.

Рассмотрим пример: getline выделяет 101 байт для line и считывает в него 100-символьную строку. Обратите внимание, что len теперь установлено на 101. Вы вызываете strsep, который находит '|' в середине строки, поэтому указывает line на то, что раньше было line+50. Теперь вы снова звоните getline. Он видит еще одну 100-символьную строку и делает вывод, что ее можно скопировать в буфер, потому что len по-прежнему равно 101. Однако, поскольку line теперь указывает на середину буфера, запись 100 символов становится неопределенным поведением.

Сделайте копию line перед вызовом strsep:

while (getline(&line, &len, data)>0) {
    char *copy = line;
    p = strsep(&copy, "|");  
    printf("Line number: %lu \t p: %s\n", count, p);
    count++;
}

Теперь line, которое вы передаете getline, сохраняется между итерациями цикла.

person Sergey Kalinichenko    schedule 28.12.2017

Посмотрите на выражение getline(&line, &len, data) и прочтите справочную страницу:

Если перед вызовом *line установлено значение NULL, а *len установлено значение 0, то getline() выделит буфер для хранения строки. Этот буфер должен быть освобожден пользовательской программой, даже если getline() не удалось.

Это должно быть так, когда вы впервые проходите цикл (хотя мы не можем видеть, где объявлено len, давайте просто предположим, что ваш реальный код делает это правильно)

Альтернативно, перед вызовом getline(), *line может содержать указатель на выделенный malloc(3) буфер размером *len байт. Если буфер недостаточно велик для размещения строки, getline() изменяет его размер с помощью realloc(3), обновляя *line и *len по мере необходимости.

Итак, если line != NULL, он должен указывать на буфер, выделенный malloc размером len. Буфер, выделенный вашим первым вызовом getline (как указано выше), удовлетворяет этому.

Обратите внимание, что line недостаточно указывать куда-то в этот буфер, это должно быть начало.

Теперь посмотрите на выражение strsep(&line,"|") и прочтите справочную страницу для < em>что:

... Этот токен завершается перезаписью разделителя нулевым байтом ('\0'), а * строка обновляется, чтобы указывать на токен

Итак, первый аргумент (line) изменен, так что вы можете снова вызвать strsep с тем же первым аргументом и получить следующий токен. Это означает, что line больше не является допустимым аргументом для getline, потому что это не начало буфера malloc (и длина len теперь также неверна).

На практике либо

  1. getline попытается прочитать len байт в буфер, который вы ему дали, но поскольку вы увеличили line на длину первого токена, он спишет конец выделенного вами блока. Это может просто повредить кучу, а не сразу умереть
  2. getline попытается перераспределить буфер, который вы ему дали, но поскольку это недопустимый выделенный блок, вы снова получите повреждение кучи.

Пока мы здесь, вы также не проверяете, что p не равно NULL, но основная проблема заключается в повреждении line.

О, и если вы считаете, что проблема связана с распределением, попробуйте использовать valgrind — обычно он находит момент, когда что-то идет не так.

person Useless    schedule 28.12.2017