Как мне исправить эту интересную ошибку getdelim / getline (динамическое распределение памяти)? - PullRequest
0 голосов
/ 04 апреля 2019

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

Задание просит нас написать программу, которая бы имитировала поведение команды / фильтра "uniq" в UNIX.

Но проблема, с которой я сталкиваюсь, связана с функциями библиотеки C getline или getdelim (мы должны использовать эти функции в соответствии со спецификациями реализации).

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

Проблема в том, что следующая строка дляцикл while while (cap = getdelim (stream.linesArray, size, '\ n', stdin))

компилируется и как-то «работает», когда я так и оставляю.Под этим я подразумеваю, что когда я выполняю программу, я ввожу произвольное количество строк произвольной длины на каждую строку, и программа не падает, но она продолжает работать, пока я не остановлю выполнение программы (правильно ли сохранены строкив "char ** linesArray;" - другая история, в которой я не уверен.

Я хотел бы иметь возможность сделать что-то вроде while ((cap = getdelim (stream.linesArray, size, '\n ', stdin)) && (cap! = -1))

, чтобы когда getdelim не читал символы в какой-либо строке (кроме EOF или \ n), то есть в самый первый раз, когда пользователь вводитпустая строка - программа перестанет принимать больше строк из стандартного ввода (и затем выведет строки, которые были сохранены в stream.linesArray с помощью getdelim).

Проблема заключается в том, что при выполнении программы, если я выполняю командуКак я уже упоминал выше, программа дает мне «Ошибка сегментации», и, честно говоря, я не знаю, почему и как я должен это исправить (я пытался что-то с этим сделать много раз, чтобы небезрезультатно).

Для справки:

https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html

https://en.cppreference.com/w/c/experimental/dynamic/getline

http://man7.org/linux/man-pages/man3/getline.3.html

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

#define DEFAULT_SIZE 20

typedef unsigned long long int ull_int;

typedef struct uniqStream
{
    char **linesArray;
    ull_int lineIndex;

}   uniq;

int main()
{
    uniq stream = { malloc(DEFAULT_SIZE * sizeof(char)), 0 };

    ull_int cap, i = 0;  
    size_t *size = 0;

    while ((cap = getdelim(stream.linesArray, size, '\n', stdin))) //&& (cap != -1))
    {
        stream.lineIndex = i;

        //if (cap == -1) { break; }
        //print("%s", stream.linesArray[i]);    

        ++i;
        if (i == sizeof(stream.linesArray))
        {
            stream.linesArray = realloc(stream.linesArray, (2 * sizeof(stream.linesArray)));
        }
    }

    ull_int j;
    for (j = 0; j < i; ++j)
    {
        printf("%s\n", stream.linesArray[j]);    
    }

    free(stream.linesArray);
    return 0;
}

1 Ответ

0 голосов
/ 04 апреля 2019

Хорошо, так что цель ясна - используйте getdelim для хранения строк внутри массива.getline сам использует динамическое распределение.Руководство совершенно ясно об этом:

getline () читает всю строку из потока, сохраняя адрес буфера, содержащего текст, в * lineptr.Буфер завершается нулем и включает символ новой строки, если таковой был найден.

getline() "сохраняет адрес буфера в * lineptr".Таким образом, lineptr должен быть действительным указателем на переменную char * (прочитайте это дважды).

* lineptr и * n будут обновлены, чтобы отразить адрес буфера и выделенный размер соответственно.

Также n должен быть действительным (!) Указателем напеременная size_t, поэтому функция может обновлять ее.

Также обратите внимание, что буфер lineptr:

Этот буфер должен быть освобожден программой пользователя, даже если getline () не удалось.

Так что же нам делать?Нам нужно иметь массив указателей на массив строк.Поскольку я не люблю становиться трехзвездочным программистом, я использую структуры.Я немного изменил ваш код, добавил несколько проверок.Вы меня извините, я не люблю typedefs, поэтому я их не использую.Переименовано с uniq на struct lines_s:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

struct line_s {
    char *line;
    size_t len;
};

struct lines_s {
    struct line_s *lines;
    size_t cnt;
};


int main() {
    struct lines_s lines = { NULL, 0 };

    // loop breaks on error of feof(stdin)
    while (1) {

        char *line = NULL;
        size_t size = 0;
        // we pass a pointer to a `char*` variable
        // and a pointer to `size_t` variable
        // `getdelim` will update the variables inside it
        // the initial values are NULL and 0
        ssize_t ret = getdelim(&line, &size, '\n', stdin);
        if (ret < 0) {
            // check for EOF
            if (feof(stdin)) {
                // EOF found - break
                break;
            }
            fprintf(stderr, "getdelim error %zd!\n", ret);
            abort();
        }

        // new line was read - add it to out container "lines"
        // always handle realloc separately
        void *ptr = realloc(lines.lines, sizeof(*lines.lines) * (lines.cnt + 1));
        if (ptr == NULL) {
            // note that lines.lines is still a valid pointer here
            fprintf(stderr, "Out of memory\n");
            abort();
        }
        lines.lines = ptr;
        lines.lines[lines.cnt].line = line;
        lines.lines[lines.cnt].len = size;
        lines.cnt += 1;


        // break if the line is "stop"
        if (strcmp("stop\n", lines.lines[lines.cnt - 1].line) == 0) {
            break;
        }
    }

    // iterate over lines
    for (size_t i = 0; i < lines.cnt; ++i) {
        // note that the line has a newline in it
        // so no additional is needed in this printf
        printf("line %zu is %s", i, lines.lines[i].line);
    }

    // getdelim returns dynamically allocated strings
    // we need to free them
    for (size_t i = 0; i < lines.cnt; ++i) {
         free(lines.lines[i].line);
    }
    free(lines.lines);
}

Для такого ввода:

 line1 line1
 line2 line2
 stop

будет выводить:

 line 0 is line1 line1
 line 1 is line2 line2
 line 2 is stop 

Проверено на onlinegdb.

Примечания:

  1. if (i == sizeof(stream.linesArray)) sizeof магически не сохраняет размер массива.sizeof(stream.linesArray) это просто sizeof(char**) это просто размер указателя.Обычно это 4 или 8 байтов, в зависимости от того, 32-битная или 64-битная архитектура.

  2. uniq stream = { malloc(DEFAULT_SIZE * sizeof(char)), - stream.linesArray - это переменная char**.Поэтому, если вы хотите иметь массив указателей на char, вы должны выделить память для указателей malloc(DEFAULT_SIZE * sizeof(char*)).

  3. typedef unsigned long long int ull_int; Тип size_t, если тип представляет размер массиваили sizeof (переменная).ssize_t иногда используется в posix api для возврата размера и состояния ошибки.Используйте эти переменные, не нужно вводить unsigned long long.
  4. ull_int cap cap = getdelim - cap не подписан, он никогда не будет cap != 1.
...