Разобрать CSV в динамически распределяемый массив структур (ANSI 89) - PullRequest
0 голосов
/ 27 июня 2019

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

Вот структура моих данных:

SO02773202,5087001,0
SO02773203,5087001,0
SO02773204,5087001,0
SO02773205,5087001,0
SO02773206,5087001,14

Вот структура, в которую я разбираю данные:

typedef struct saleslines{
  char* salesid;
  char* smmcampaignid;
  int numberofbottles;
} saleslines_t;

Вот моя попытка разбора файла:

int read_saleslines(saleslines_t* saleslines, int number_of_lines){
  char c;

  FILE* fp; 
  fp = fopen(FILENAME, "r");             /* Open the saleslines file */

  if(fp == NULL){                              /* Crash if file not found */
  printf("Error - file not found\n");
    return 0;
  }

  c = getc(fp);
  while (c != EOF){
    if (c == '\n'){
    number_of_lines += 1;
    }
    c = getc(fp);
  }

  printf("Number of lines is %d\n", number_of_lines);

  saleslines = (saleslines_t*) malloc((number_of_lines * 2) * sizeof(saleslines_t));

  /* allocation of the buffer for every line in the File */
  char *buf = (char*) malloc(1000);
  char *tmp; 

  if ( ( fp = fopen(FILENAME, "r" ) ) == NULL )
  {
    printf( "File could not be opened.\n" );
  }
  int i = 0;
  while (fgets(buf, 255, fp) != NULL){
    if ((strlen(buf)>0) && (buf[strlen (buf) - 1] == '\n'))
      buf[strlen (buf) - 1] = '\0';       

    tmp = strtok(buf, ",");
    saleslines[i].salesid = strdup(tmp);

    tmp = strtok(NULL, ",");
    saleslines[i].smmcampaignid = strdup(tmp);

    tmp = strtok(NULL, ",");
    saleslines[i].numberofbottles = atoi(tmp);

    printf("Salesid: %s\nCampaign: %s\nBottles: %i\n\n", saleslines[i].salesid , saleslines[i].smmcampaignid, saleslines[i].numberofbottles);

    i++;
  }
  free(buf);
  fclose(fp);
  printf("Number of lines is %i\n", number_of_lines);
  return number_of_lines;
}

По какой-то причине он анализирует файл и печатает полученный массивиз структур, однако, когда я вызываю эту функцию сразу после этого, она падает с segfault:

void print_saleslines_struct(saleslines_t* saleslines, int number_of_lines{
  int i;
  printf("Number of lines is %i", number_of_lines);
  for(i = 0; i < number_of_lines; i++){
    printf("Salesid:\t %s\n", saleslines[i].salesid);
    printf("Campaign:\t %s\n", saleslines[i].smmcampaignid);
    printf("# of Bottles:\t %d\n", saleslines[i].numberofbottles);
  }
}

Я не могу найти, где эта ошибка памяти.

Вот инициализацияи главное:

saleslines_t* saleslines;
saleslines_summary_t* saleslines_summary;
saleslines_grouped_t* saleslines_grouped;
int number_of_lines = 0;
int* number_of_linesp = &number_of_lines;

/* Main */

int main(){

  int chosen_option;

  while(1){

    printf("What would you like to do?\n");
    printf("1. Read saleslines.txt\n");
    printf("2. Print saleslines\n");
    printf("3. Summarise saleslines\n");
    printf("4. Exit the program\n");

    scanf("%d", &chosen_option);

    switch(chosen_option){

    /*  case 1 : number_of_lines = read_saleslines_file(saleslines, number_of_lines); break; */

      case 1 : number_of_lines = read_saleslines(saleslines, number_of_lines); break;

      case 2 : printf("Number of lines is %i", number_of_lines);  print_saleslines_struct(saleslines, number_of_lines); break;

      case 3 : summarise_saleslines(saleslines, number_of_linesp, saleslines_summary, saleslines_grouped); break;

      case 4 : free(saleslines); free(saleslines_summary); free(saleslines_grouped); return 0;   

    }

  }

  return 0;

}

Обновление

Кажется, проблема связана с моей инициализацией массива структур.

Когда я инициализирую его какэто: saleslines_t* saleslines; и затем malloc, как это: saleslines = malloc(number_of_lines + 1 * sizeof(saleslines_t);

Я получаю segfault.

Но если я инициализирую так: saleslines[600]; (выделение больше, чем количество строк вфайл), все работает.

Как обойти это?Я хотел бы иметь возможность динамически распределять количество записей в массиве struct.

Изменить 2

Вот изменения, как предлагается:

int read_saleslines(saleslines_t** saleslines, int number_of_lines);

saleslines_t* saleslines;
int number_of_lines = 0;

int main(){

  while(1){

    printf("What would you like to do?\n");
    printf("1. Read saleslines.txt\n");
    printf("2. Print saleslines\n");
    printf("3. Summarise saleslines\n");
    printf("4. Exit the program\n");

    printf("Number of saleslines = %i\n", number_of_lines);

    scanf("%d", &chosen_option);

    switch(chosen_option){

    /*  case 1 : number_of_lines = read_saleslines_file(saleslines, number_of_lines); break; */

      case 1 : number_of_lines = read_saleslines(&saleslines, number_of_lines); break;

      case 2 : printf("Number of lines is %i", number_of_lines);  print_saleslines_struct(saleslines, number_of_lines); break;

      case 3 : summarise_saleslines(saleslines, number_of_linesp, saleslines_summary, saleslines_grouped); break;

      case 4 : free(saleslines); free(saleslines_summary); free(saleslines_grouped); return 0;   

    }

  }

  return 0;

}

int read_saleslines(saleslines_t** saleslines, int number_of_lines)
{

  char c;

  FILE* fp; 
  fp = fopen(FILENAME, "r");             /* Open the saleslines file */

  if(fp == NULL){                              /* Crash if file not found */
  printf("Error - file not found\n");
    return 0;
  }

  c = getc(fp);
  while (c != EOF){
    if (c == '\n'){
    number_of_lines += 1;
    }
    c = getc(fp);
  }

  fclose(fp);

  printf("Number of lines is %d\n", number_of_lines);

  *saleslines = (saleslines_t*) malloc((number_of_lines + 1) * sizeof(saleslines_t));

  /* allocation of the buffer for every line in the File */
  char *buf = malloc(25);
  char *tmp; 

  if ( ( fp = fopen(FILENAME, "r" ) ) == NULL )
  {
    printf( "File could not be opened.\n" );
  }
  int i = 0;
  while (fgets(buf, 25, fp) != NULL){
    if ((strlen(buf)>0) && (buf[strlen (buf) - 1] == '\n'))
      buf[strlen (buf) - 1] = '\0';       

    tmp = strtok(buf, ",");
    (*saleslines)[i].salesid = strdup(tmp);

    tmp = strtok(NULL, ",");
    (*saleslines)[i].smmcampaignid = strdup(tmp);

    tmp = strtok(NULL, ",");
    (*saleslines)[i].numberofbottles = atoi(tmp);

    printf("Salesid: %s\nCampaign: %s\nBottles: %i\n\n", saleslines[i]->salesid , saleslines[i]->smmcampaignid, saleslines[i]->numberofbottles);

    i++;
  }
  free(buf);
  fclose(fp);
  printf("Number of lines is %i\n", number_of_lines);
  return number_of_lines;
}

Программа теперь работает с ошибками после чтения первого элемента в массиве struct.

Ответы [ 2 ]

1 голос
/ 27 июня 2019

У вас есть большое количество ошибок в вашем коде, и с вашим подходом в целом. Нет необходимости делать два прохода по файлу, чтобы определить количество строк перед выделением, а затем перечитать файл в попытке проанализировать данные. Кроме того, нет необходимости маркировать каждую строку для разделения значений, разделенных запятыми, sscanf() для анализа двух строк, и здесь достаточно одного int после чтения каждой строки с fgets.

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

Кроме того, обычно вы хотите открыть и проверить файл в вызывающей программе и передать параметр FILE*, передающий поток открытых файлов вашей функции. Имея это в виду, вы можете изменить свою функцию следующим образом:

/* read saleslines into array of saleslines_t, allocating for
 * salesid, and smmcampaignid within each struct. Return pointer
 * to allocated array on success with lines updated to hold the
 * number of elements, or NULL otherwise.
 */
saleslines_t *read_saleslines (FILE *fp, size_t *lines)
{

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

    char buf[MAXC];                 /* buffer to hold line */
    size_t maxlines = MINL;         /* maxlines allocated */
    saleslines_t *sales = NULL;     /* pointer to array of struct */

( примечание: , поскольку вы отслеживаете количество строк, прочитанных через указатель lines, переданных в качестве параметра, имеет смысл инициализировать значение по этому адресу в ноль)

Теперь начинается работа вашей функции, вы хотите прочитать каждую строку в buf и проанализировать необходимую информацию из каждой строки. Поскольку salesid и smmcampaignid являются указателями на символ в вашей структуре, вам необходимо выделить блок памяти для каждой строки, анализируемой в строке, скопировать строку в новый блок памяти и затем назначить Начальный адрес Бока к каждому из ваших указателей. Чтобы «динамически» обрабатывать размещение элементов для вашей структуры, вы просто проверяете, равно ли количество заполненных строк (*lines) по сравнению с выделенным числом (maxlines), (или если *lines равно нулю, указывая на необходимость начального allocation) и realloc в обоих случаях либо realloc (или заново выделять) хранилище для вашего массива структуры.

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

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

    while (fgets (buf, MAXC, fp)) { /* read each line in file */
        char id[MAXC], cid[MAXC];   /* temp arrays to hold strings */
        int bottles;                /* temp int for numberofbottles */
        if (*lines == maxlines || !*lines) {    /* check if realloc req'd */
            /* always realloc with a temp pointer */
            void *tmp = realloc (sales, 2 * maxlines * sizeof *sales);
            if (!tmp) { /* if realloc fails, original pointer still valid */
                perror ("realloc-sales");   /* throw error */
                return sales;               /* return current pointer      */ 
            }                               /* (don't exit or return NULL) */
            sales = tmp;    /* assign reallocated block to sales */
            /* (optional) zero newly allocated memory */
            memset (sales + *lines, 0, maxlines * sizeof *sales);
            maxlines *= 2;  /* update maxlines allocated */
        }

Теперь вы готовы анализировать требуемую информацию из вашей строки с помощью sscanf, а затем, после успешного анализа информации, вы можете выделить для каждого из ваших salesid и smmcampaignid указателей скопировать проанализированную информацию в новые блоки памяти, присваивающие начальный адрес каждому указателю соответственно, например,

        /* parse needed data from line (sscanf is fine here) */
        if (sscanf (buf, "%1023[^,],%1023[^,],%d", id, cid, &bottles) == 3) {
            size_t  idlen  = strlen (id),   /* get lengths of strings */
                    cidlen = strlen (cid);
            sales[*lines].salesid = malloc (idlen + 1); /* allocate string */
            if (!sales[*lines].salesid) {               /* validate! */
                perror ("malloc-sales[*lines].salesid");
                break;
            }
            sales[*lines].smmcampaignid = malloc (cidlen + 1);  /* ditto */
            if (!sales[*lines].smmcampaignid) {
                perror ("malloc-sales[*lines].smmcampaignid");
                break;
            }
            memcpy (sales[*lines].salesid, id, idlen + 1);  /* copy strings */
            memcpy (sales[*lines].smmcampaignid, cid, cidlen + 1);
            sales[(*lines)++].numberofbottles = bottles;    /* assign int */
        }   /* (note lines counter updated in last assignment) */

( примечание: вы можете использовать strdup, чтобы получить длину каждой проанализированной строки и выделить достаточно памяти для хранения строки и назначить ее указателю за один раз, например, sales[*lines].salesid = strdup (id); , но ... strdup не требуется включать в C99 или более позднюю версию, поэтому получить длину, выделить length + 1 байт, а затем memcpy вашу строку так же просто, чтобы обеспечить переносимость. strdup выделяет память, вы должны проверить возвращенный указатель - что-то упускают из виду 99% тех, кто его использует.)

Вот и все, когда fgets() терпит неудачу, вы достигли EOF, теперь просто:

    return sales;   /* return dynamically allocated array of struct */
}

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

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */
#define MINL    2

typedef struct saleslines{
    char *salesid;
    char *smmcampaignid;
    int numberofbottles;
} saleslines_t;

/* read saleslines into array of saleslines_t, allocating for
 * salesid, and smmcampaignid within each struct. Return pointer
 * to allocated array on success with lines updated to hold the
 * number of elements, or NULL otherwise.
 */
saleslines_t *read_saleslines (FILE *fp, size_t *lines)
{
    char buf[MAXC];                 /* buffer to hold line */
    size_t maxlines = MINL;         /* maxlines allocated */
    saleslines_t *sales = NULL;     /* pointer to array of struct */

    *lines = 0;     /* zero lines */

    while (fgets (buf, MAXC, fp)) { /* read each line in file */
        char id[MAXC], cid[MAXC];   /* temp arrays to hold strings */
        int bottles;                /* temp int for numberofbottles */
        if (*lines == maxlines || !*lines) {    /* check if realloc req'd */
            /* always realloc with a temp pointer */
            void *tmp = realloc (sales, 2 * maxlines * sizeof *sales);
            if (!tmp) { /* if realloc fails, original pointer still valid */
                perror ("realloc-sales");   /* throw error */
                return sales;               /* return current pointer      */ 
            }                               /* (don't exit or return NULL) */
            sales = tmp;    /* assign reallocated block to sales */
            /* (optional) zero newly allocated memory */
            memset (sales + *lines, 0, maxlines * sizeof *sales);
            maxlines *= 2;  /* update maxlines allocated */
        }
        /* parse needed data from line (sscanf is fine here) */
        if (sscanf (buf, "%1023[^,],%1023[^,],%d", id, cid, &bottles) == 3) {
            size_t  idlen  = strlen (id),   /* get lengths of strings */
                    cidlen = strlen (cid);
            sales[*lines].salesid = malloc (idlen + 1); /* allocate string */
            if (!sales[*lines].salesid) {               /* validate! */
                perror ("malloc-sales[*lines].salesid");
                break;
            }
            sales[*lines].smmcampaignid = malloc (cidlen + 1);  /* ditto */
            if (!sales[*lines].smmcampaignid) {
                perror ("malloc-sales[*lines].smmcampaignid");
                break;
            }
            memcpy (sales[*lines].salesid, id, idlen + 1);  /* copy strings */
            memcpy (sales[*lines].smmcampaignid, cid, cidlen + 1);
            sales[(*lines)++].numberofbottles = bottles;    /* assign int */
        }   /* (note lines counter updated in last assignment) */
    }

    return sales;   /* return dynamically allocated array of struct */
}

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

    saleslines_t *sales = NULL; /* pointer to saleslines_t */
    size_t nlines;
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

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

    sales = read_saleslines (fp, &nlines);  /* read saleslines */

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

    for (size_t i = 0; i < nlines; i++) {   /* loop over each */
        printf ("sales[%2zu]:  %s  %s  %2d\n", i, sales[i].salesid,
                sales[i].smmcampaignid, sales[i].numberofbottles);
        free (sales[i].salesid);        /* free salesid */
        free (sales[i].smmcampaignid);  /* free smmcampaignid */
    }
    free (sales);   /* free sales */

    return 0;
}

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

$ ./bin/saleslines dat/saleslines.txt
sales[ 0]:  SO02773202  5087001   0
sales[ 1]:  SO02773203  5087001   0
sales[ 2]:  SO02773204  5087001   0
sales[ 3]:  SO02773205  5087001   0
sales[ 4]:  SO02773206  5087001  14

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

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

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

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

$ valgrind ./bin/saleslines dat/saleslines.txt
==19819== Memcheck, a memory error detector
==19819== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==19819== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==19819== Command: ./bin/saleslines dat/saleslines.txt
==19819==
sales[ 0]:  SO02773202  5087001   0
sales[ 1]:  SO02773203  5087001   0
sales[ 2]:  SO02773204  5087001   0
sales[ 3]:  SO02773205  5087001   0
sales[ 4]:  SO02773206  5087001  14
==19819==
==19819== HEAP SUMMARY:
==19819==     in use at exit: 0 bytes in 0 blocks
==19819==   total heap usage: 13 allocs, 13 frees, 935 bytes allocated
==19819==
==19819== All heap blocks were freed -- no leaks are possible
==19819==
==19819== For counts of detected and suppressed errors, rerun with: -v
==19819== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Нет ничего сложного в динамическом выделении чего-либо. Просто возьмите его достаточно маленькими кусочками, чтобы разбить все "I's" и пересечь все "T's" для каждого указателя, требующего выделения. Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

1 голос
/ 27 июня 2019

У вас проблема с аргументами read_saleslines().Первый аргумент должен быть указателем на массив ваших структур, что означает двойной указатель.

В

int read_saleslines(saleslines_t* saleslines, int number_of_lines){

вы хотите изменить, куда указывает saleslines.saleslines является локальной переменной функции, и область действия - это функция.После выхода из read_saleslines() переменная «уничтожается», что означает, что значение, которое она содержит, больше не доступно.Добавив еще один уровень косвенности, указатель, вы можете изменить переменную, определенную вне функции, которая является (некрасивой) глобальной или другой.Итак, измените этот аргумент так, чтобы прототип функции совпадал с

int read_saleslines(saleslines_t** saleslines, int *);

и измените места, где вы к нему обращаетесь внутри функции (добавив * для доступа к ней, например:

saleslines = (saleslines_t*) malloc((number_of_lines * ...

до

*saleslines = (saleslines_t*) malloc((number_of_lines * ...

и

saleslines[i].salesid = strdup(tmp);

до

(*saleslines)[i].salesid = strdup(tmp);

Затем добавьте &, где вы используете переменнуювне функции:

number_of_lines = read_saleslines(saleslines, number_of_lines);

изменяется на

some_var = read_saleslines(&saleslines, &number_of_lines);

Это заставит вас работать с кодом.

...