Чтение текстового файла с использованием fgets () и strtok () для разделения строк в строке, что приводит к нежелательному поведению - PullRequest
0 голосов
/ 21 декабря 2018

Я пытаюсь прочитать текстовый файл в следующем формате, используя fgets () и strtok ().

1082018 1200 79 Meeting with President
2012018 1200 79 Meet with John at cinema
2082018 1400 30 games with Alpha
3022018 1200 79 sports

Мне нужно отделить первое значение от остальной строки, например:

key=21122019, val = 1200 79 Meeting with President

Для этого я использую strchr() для val и strtok() для key, однако значение ключа остается неизменным при чтении из файла.Я не могу понять, почему это происходит, поскольку я выделяю пространство для in_key внутри цикла while и каждый раз помещаю его в массив с другим индексом.

Мой код:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 1000 // max number of lines to be read
#define VALLEN 100
#define MAXC 1024

#define ALLOCSIZE 1000 /*size of available space*/
static char allocbuf[ALLOCSIZE]; /* storage for alloc*/
static char *allocp = allocbuf; /* next free position*/

char *alloc(int n) { /* return a pointer to n characters*/
    if (allocbuf + ALLOCSIZE - allocp >= n) { /*it fits*/
        allocp += n;
        return allocp - n; /*old p*/
    } else /*not enough room*/
        return 0;
}

int main(int argc, char** argv) {
    FILE *inp_cal;
    inp_cal = fopen("calendar.txt", "r+");

    char buf[MAXC];
    char *line[1024];
    char *p_line;

    char *in_val_arr[100];
    char *in_key_arr[100];
    int count = 0;
    char delimiter[] = " ";

    if (inp_cal) {
        printf("Processing file...\n");
        while (fgets(buf, MAXC, inp_cal)) {
            p_line = malloc(strlen(buf) + 1); // malloced with size of buffer.
            char *in_val;
            char *in_key;

            strcpy(p_line, buf);    //used to create a copy of input buffer
            line[count] = p_line;

            /* separating the line based on the first space. The words after
             * the delimeter will be copied into in_val */
            char *copy = strchr(p_line, ' ');
            if (copy) {
                if ((in_val = alloc(strlen(line[count]) + 1)) == NULL) {
                    return -1;
                } else {
                    strcpy(in_val, copy + 1);
                    printf("arr: %s", in_val);
                    in_val_arr[count] = in_val;
                }
            } else
                printf("Could not find a space\n");

            /* We now need to get the first word from the input buffer*/
            if ((in_key = alloc(strlen(line[count]) + 1)) == NULL) {
                return -1;
            }
            else {
                in_key = strtok(buf, delimiter);
                printf("%s\n", in_key);
                in_key_arr[count] = in_key; // <-- Printed out well
                count++;
            }
        }
        for (int i = 0; i < count; ++i)
            printf("key=%s, val = %s", in_key_arr[i], in_val_arr[i]); //<-- in_key_arr[i] contains same values throughout, unlike above
        fclose(inp_cal);
    }
    return 0;
}

вывод цикла while (правильный):

Processing file...
arr: 1200 79 Meeting with President
1082018
arr: 1200 79 Meet with John at cinema
2012018
arr: 1400 30 games with Alpha
2082018
arr: 1200 79 sports
3022018

вывод цикла for (неправильный):

key=21122019, val = 1200 79 Meeting with President
key=21122019, val = 1200 79 Meet with John
key=21122019, val = 1400 30 games with Alpha
key=21122019, val = 1200 79 sports

Есть предложения о том, как это можно улучшить и почему это происходит?Спасибо

Ответы [ 3 ]

0 голосов
/ 21 декабря 2018

Вы назначаете адрес с помощью вызова для выделения, а затем переназначаете с помощью вызова в strtok?переписываете тот же адрес?Скопировать возврат из strtok в in_key?

       char *copy = strchr(p_line, ' ');
        if (copy) {
            if ((in_val = alloc(strlen(line[count]) + 1)) == NULL) {
                return -1;
            } else {
                printf("arr: %ul\n", in_val);
                strcpy(in_val, copy + 1);
                printf("arr: %s", in_val);
                in_val_arr[count] = in_val;
            }
        } else
            printf("Could not find a space\n");

        /* We now need to get the first word from the input buffer*/
        if ((in_key = alloc(strlen(line[count]) + 1)) == NULL) {
            return -1;
        }
        else {

            printf("key: %ul\n", in_key);
            in_key = strtok(buf, delimiter);
            printf("key:\%ul %s\n",in_key, in_key);
            in_key_arr[count++] = in_key; // <-- Printed out well
        }

вывод:

allocbuf: 1433760064l
Processing file...
all: 1433760064l
arr: 1433760064l
arr: 1200 79 Meeting with President
all: 1433760104l
key: 1433760104l
key:4294956352l 1082018

это изменение исправило это:

strcpy(in_key, strtok(buf, delimiter));
0 голосов
/ 21 декабря 2018

Продолжая комментарий, пытаясь использовать strtok для разделения ваших данных на key, val, somenum и оставшуюся часть строки в виде строки, вы делаете вещи сложнее, чем нужно.

Еслиначало вашей строки всегда:

key val somenum rest

вы можете просто использовать sscanf для разбора key, val и somenum, например, три значения unsigned и остальная часть строки в строку,Чтобы сохранить связь между каждым key, val, somenum и string, сохранение значений из каждой строки в struct значительно облегчает отслеживание всего.Вы можете даже выделить string, чтобы минимизировать объем хранилища до требуемой суммы.Например, вы можете использовать что-то вроде следующего:

typedef struct {    /* struct to handle values */
    unsigned key, val, n;
    char *s;
} keyval_t;

Затем в main() вы можете выделить какое-то начальное число структуры, сохранить индекс в качестве счетчика, циклически читать каждую строку, используя временную структуру.и буфер, затем выделение для строки (+1 для символа, заканчивающегося нулем ) и копирование значений в вашу структуру.Когда количество заполненных структур достигнет выделенного вами количества, просто realloc количество структур и продолжайте движение.

Например, предположим, что вы изначально выделяете NSTRUCT распорок и читаете каждую строку в bufНапример,

...
#define NSTRUCT    8    /* initial struct to allocate */
#define MAXC    1024    /* read buffer size (don't skimp) */
...
    /* allocate/validate storage for max struct */
    if (!(kv = malloc (max * sizeof *kv))) {
        perror ("malloc-kv");
        return 1;
    }
    ...
    size_t ndx = 0,         /* used */
        max = NSTRUCT;      /* allocated */
    keyval_t *kv = NULL;    /* ptr to struct */
    ...
    while (fgets (buf, MAXC, fp)) { /* read each line of input */
    ...

В вашем цикле while вам просто нужно проанализировать значения с помощью sscanf, например,

        char str[MAXC];
        size_t len;
        keyval_t tmp = {.key = 0};  /* temporary struct for parsing */
        if (sscanf (buf, "%u %u %u %1023[^\n]", &tmp.key, &tmp.val, &tmp.n,
            str) != 4) {
            fprintf (stderr, "error: invalid format, line '%zu'.\n", ndx);
            continue;
        }

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

        if (ndx == max) {    /* check if realloc needed */
            /* always realloc with temporary pointer */
            void *kvtmp = realloc (kv, 2 * max * sizeof *kv);
            if (!kvtmp) {
                perror ("realloc-kv");
                break;  /* don't exit, kv memory still valid */
            }
            kv = kvtmp; /* assign new block to pointer */
            max *= 2;   /* increment max allocated */
        }

Теперь с хранилищем для struct просто получитедлину строки, скопируйте значения unsigned в свою структуру и выделите length + 1 символов для kv[ndx].s и скопируйте str в kv[ndx].s, например,

        len = strlen(str);              /* get length of str */
        kv[ndx] = tmp;                  /* assign tmp values to kv[ndx] */
        kv[ndx].s = malloc (len + 1);   /* allocate block for str */
        if (!kv[ndx].s) {               /* validate */
            perror ("malloc-kv[ndx].s");
            break;  /* ditto */
        }
        memcpy (kv[ndx++].s, str, len + 1); /* copy str to kv[ndx].s */
    }

(примечание: вы можете использовать strdup, если он у вас есть, чтобы заменить malloc - memcpy на kv[ndx].s = strdup (str);, но, поскольку strdup выделяет, не забудьте проверить kv[ndx].s != NULL перед увеличением ndxесли вы идете по этому пути)

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

    for (size_t i = 0; i < ndx; i++) {
        printf ("kv[%2zu] : %8u  %4u  %2u  %s\n", i,
            kv[i].key, kv[i].val, kv[i].n, kv[i].s);
        free (kv[i].s);     /* free string */
    }

    free (kv);  /* free stucts */

(не забудьте освободить выделенную память)

Поместив его в целом, выможет сделать что-то вроде следующего:

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

#define NSTRUCT    8    /* initial struct to allocate */
#define MAXC    1024    /* read buffer size (don't skimp) */

typedef struct {    /* struct to handle values */
    unsigned key, val, n;
    char *s;
} keyval_t;

int main (int argc, char **argv) {

    char buf[MAXC];         /* line buffer */
    size_t ndx = 0,         /* used */
        max = NSTRUCT;      /* allocated */
    keyval_t *kv = NULL;    /* ptr to struct */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-file");
        return 1;
    }

    /* allocate/validate storage for max struct */
    if (!(kv = malloc (max * sizeof *kv))) {
        perror ("malloc-kv");
        return 1;
    }

    while (fgets (buf, MAXC, fp)) { /* read each line of input */
        char str[MAXC];
        size_t len;
        keyval_t tmp = {.key = 0};  /* temporary struct for parsing */
        if (sscanf (buf, "%u %u %u %1023[^\n]", &tmp.key, &tmp.val, &tmp.n,
            str) != 4) {
            fprintf (stderr, "error: invalid format, line '%zu'.\n", ndx);
            continue;
        }
        if (ndx == max) {    /* check if realloc needed */
            /* always realloc with temporary pointer */
            void *kvtmp = realloc (kv, 2 * max * sizeof *kv);
            if (!kvtmp) {
                perror ("realloc-kv");
                break;  /* don't exit, kv memory still valid */
            }
            kv = kvtmp; /* assign new block to pointer */
            max *= 2;   /* increment max allocated */
        }
        len = strlen(str);              /* get length of str */
        kv[ndx] = tmp;                  /* assign tmp values to kv[ndx] */
        kv[ndx].s = malloc (len + 1);   /* allocate block for str */
        if (!kv[ndx].s) {               /* validate */
            perror ("malloc-kv[ndx].s");
            break;  /* ditto */
        }
        memcpy (kv[ndx++].s, str, len + 1); /* copy str to kv[ndx].s */
    }

    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

    for (size_t i = 0; i < ndx; i++) {
        printf ("kv[%2zu] : %8u  %4u  %2u  %s\n", i,
            kv[i].key, kv[i].val, kv[i].n, kv[i].s);
        free (kv[i].s);     /* free string */
    }

    free (kv);  /* free stucts */
}

Пример использования / вывода

Используя ваш файл данных в качестве ввода, вы получите следующее:

$ ./bin/fgets_sscanf_keyval <dat/keyval.txt
kv[ 0] :  1082018  1200  79  Meeting with President
kv[ 1] :  2012018  1200  79  Meet with John at cinema
kv[ 2] :  2082018  1400  30  games with Alpha
kv[ 3] :  3022018  1200  79  sports

Использование памяти / проверка ошибок

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

Этокрайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что высвободновся память, которую вы выделили.

Для Linux valgrind - нормальный выбор.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind ./bin/fgets_sscanf_keyval <dat/keyval.txt
==6703== Memcheck, a memory error detector
==6703== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==6703== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==6703== Command: ./bin/fgets_sscanf_keyval
==6703==
kv[ 0] :  1082018  1200  79  Meeting with President
kv[ 1] :  2012018  1200  79  Meet with John at cinema
kv[ 2] :  2082018  1400  30  games with Alpha
kv[ 3] :  3022018  1200  79  sports
==6703==
==6703== HEAP SUMMARY:
==6703==     in use at exit: 0 bytes in 0 blocks
==6703==   total heap usage: 5 allocs, 5 frees, 264 bytes allocated
==6703==
==6703== All heap blocks were freed -- no leaks are possible
==6703==
==6703== For counts of detected and suppressed errors, rerun with: -v
==6703== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Посмотрите вещии позвольте мне сейчас, если у вас есть дополнительные вопросы.Если вам нужно еще больше разбить kv[i].s, то вы можете подумать об использовании strtok.

0 голосов
/ 21 декабря 2018

Вы сохраняете один и тот же указатель в in_key_arr снова и снова.

Вам это примерно необходимо:

in_key = strtok(buf, delimiter);
printf("%s\n", in_key);
char *newkey = malloc(strlen(in_key) + 1);  // <<<< allocate new memory
strcpy(newkey, in_key);
in_key_arr[count] = newkey;                 // <<<< store newkey
count++;

Отказ от ответственности:

  • для краткости проверка ошибок не производится
  • когда вы закончите с ней, необходимо освободить память с ошибками.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...