Структурно-динамический массив - PullRequest
0 голосов
/ 23 ноября 2018

Я новичок в c и прошу прощения за мой плохой английский.

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

Texas 03/03/2015 1
California 06/02/2013 5
Utah 03/01/2014 10
....

Попробуйте с помощью scanf () (не сообщайте об основном, потому что в нем нет проблем.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum mese_e {Gen=1, Feb, Mar, Apr, Mag, Giu, Lug, Ago, Set, Ott, Nov, Dic} tipo_mese;
typedef struct data_s
{
    int giorno;
    tipo_mese mese;
    int anno;
} tipo_data;

typedef struct dati_file_s
{
    char* regione;
    tipo_data data;
    int mm_pioggia;
} tipo_dati_file;

typedef struct ritorna_s
{
    tipo_dati_file* array;
    int count;
} tipo_ritorna;

int conta_righe(char* Nome_f)
{
    int i=0;
    char c;
    FILE* file;
    file=fopen(Nome_f,"r");
    while ((c=fgetc(file))!=EOF)
    {if(c=='\n')
    i++;}
    fclose(file);
    return i;

}
void crea_array (char* Nome_f)
{
    int i,n;
    char* regione= (char*)malloc(sizeof(char));
    tipo_data data;
    int mm_pioggia;
    tipo_ritorna risultati;
    FILE* file;
    n = conta_righe(Nome_f);
    printf("%d\n",n);
    tipo_dati_file* array = (tipo_dati_file*) malloc (n*sizeof (tipo_dati_file));
    file = fopen(Nome_f,"r");
    if( file==NULL )
        {
            printf("Errore in apertura del file!");
            exit(1);
        }
    for(i=0; i<=n; i++)
    {
        fscanf(file,"%s %d/%d/%d %d\n",regione, &data.giorno, &data.mese, &data.anno, &mm_pioggia);
        strcpy(array[i].regione, regione);
        array[i].data.giorno=data.giorno;
        array[i].data.mese= data.mese;
        array[i].data.anno= data.anno;
        array[i].mm_pioggia= mm_pioggia;
        printf("%s %d/%d/%d %d\n",array[i].regione,array[i].data.giorno, array[i].data.mese,array[i].data.anno,array[i].mm_pioggia);
        }

    fclose(file);
}

попробуйте с помощью fgets ()

#include <stdio.h>
#include <stdlib.h>
#include <string.h> typedef enum mese_e {Gen=1, Feb, Mar, Apr, Mag, Giu, Lug, Ago, Set, Ott, Nov, Dic} tipo_mese; typedef struct data_s {
    int giorno;
    tipo_mese mese;
    int anno; } tipo_data;

typedef struct dati_file_s {
    char* regione;
    tipo_data data;
    int mm_pioggia; } tipo_dati_file;

typedef struct ritorna_s {
    tipo_dati_file* array;
    int count; } tipo_ritorna;

int conta_righe(char* Nome_f) {
    int i=0;
    char c;
    FILE* file;
    file=fopen(Nome_f,"r");
    while ((c=fgetc(file))!=EOF)
    {if(c=='\n')
    i++;}
    fclose(file);
    return i;

} void crea_array (char* Nome_f, int v) {
    int i=0,s;
    char* r;
    //tipo_ritorna risultati;
    FILE* file;
    //n = conta_righe(file);
    tipo_dati_file* array = (tipo_dati_file*) malloc (v*sizeof (tipo_dati_file));
    file = fopen(Nome_f,"r");

    if( file==NULL )
        {
            printf("Errore in apertura del file!");
            exit(1);
        }
    if (feof(file)==0)
    {
        char* buf= (char*) malloc(v*sizeof(char));
        /*while ( fgets( buf,10000, file) != NULL )
        {
            r = sscanf( buf, "%s% d/%d/%d %d\n", array[i].regione, &array[i].data.giorno, &array[i].data.mese, &array[i].data.anno, &array[i].mm_pioggia);
            printf("%s %d/%d/%d %d\n", array[i].regione, array[i].data.giorno, array[i].data.mese, array[i].data.anno, array[i].mm_pioggia);
            i++;
        }*/
        while(1)
        {
            r=fgets( buf,1000, file);
            if (r!=NULL)
            {
                printf("%s",buf);
                sscanf( buf, "%s% d/%d/%d %d\n", array[i].regione, &array[i].data.giorno, &array[i].data.mese, &array[i].data.anno, &array[i].mm_pioggia);
                printf("%s %d/%d/%d %d\n", array[i].regione, array[i].data.giorno, array[i].data.mese, array[i].data.anno, array[i].mm_pioggia);
                i++;
            }
               else exit(1);
        }
    }
    else exit(1);

    fclose(file); }

1 Ответ

0 голосов
/ 24 ноября 2018

У вас есть две основные проблемы, которые я вижу в crea_array,

  1. Вы объявляете char* r;, но затем пытаетесь присвоить возврат sscanf (buf, "%s %d/%d/%d %d\n", ... (например, r = sscanf (.... Это неверно.sscanf возвращает тип int, представляющий количество успешных преобразований, которые имели место, как указано в строке формата (например, "%s %d/%d/%d %d\n" вернет 5 в случае успеха, а удалит '\n', это вызовет проблемы.) Ваш компилятор должен кричать на вас предупреждения. Если нет, вам нужно включить предупреждения компилятора, добавив -Wall -Wextra -pedantic в качестве параметров компилятора и не принимать код, пока он не скомпилируется безодиночное предупреждение .
  2. crea_array должно быть объявлено как тип tipo_dati_file * и должно содержать return array; в конце. Вы должны назначить возврат указателю обратно в вызывающей стороне. Вы также должны *За 1025 * до return или вы только что создали утечку памяти , поскольку невозможно free() памяти, выделенной для buf после возврата из функции. (Далее, если вы простовыделяя 1000-char каждыйвремя, просто используйте фиксированный буфер, например, char buf[1000]; и избавьте от необходимости выделять buf полностью.

В целом, вы можете сделать что-то похожее на:

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

tipo_dati_file *crea_array (char* Nome_f, int v) 
{
    int i = 0,
        s,
        r;
    char buf[MAXC] = "";
    //tipo_ritorna risultati;
    FILE* file;

    //n = conta_righe(file);
    tipo_dati_file *array = malloc (v * sizeof *array);
    file = fopen (Nome_f, "r");

    if (file == NULL) {
        printf ("Errore in apertura del file!");
        exit (EXIT_FAILURE);
    }

    if (!array) {   /* if you allocate, you must validate - every time */
        perror ("malloc-array");
        exit (EXIT_FAILURE);
    }

    while (fgets (buf, MAXC, file) != NULL)
    {
        r = sscanf (buf, "%s %d/%d/%d %d", array[i].regione, 
                    &array[i].data.giorno, &array[i].data.mese, 
                    &array[i].data.anno, &array[i].mm_pioggia);

        if (r != 5) {   /* validate return of every (s)scanf funciton */
            fput ("error: failed to parse buf.\n", stderr);
            continue;   /* get next line */
        }

        printf ("%s %d/%d/%d %d\n", array[i].regione, array[i].data.giorno, 
                array[i].data.mese, array[i].data.anno, array[i].mm_pioggia);
        i++;
    }

    fclose (file);

    return array; 
}

Примечание: Я не скомпилировал приведенный выше код.

Тогда в main вы можете сделать что-то похожее на:

tipo_dati_file *array = crea_array (name, v);

(примечание: вы также должны передать 3-й параметр типа int *numelem, чтобы вы могли присвоить *numelem = i; перед возвратом, сделав доступным количество заполненных элементов обратно в вызывающей стороне, если фактически прочитано меньше v)

Если вы опубликуете Минимальный, полный и проверяемый пример (MCVE) вместе с образцом файла данных (10 строк или около того), я буду рад помочь вам в дальнейшем и могу подтвердитькод работает - поскольку у меня будет что-то, что я могу скомпилировать и запустить.

Редактировать следующие предупреждения опубликовано (в комментариях)

Два предупреждения подробно рассматриваются вкомментарии под ответом.Как только они будут решены, вы столкнетесь с ужасным SegFault, так как не выделяете хранилище для array[i].regione.

В вашем вложенном наборе структур:

typedef struct dati_file_s {
    char* regione;
    tipo_data data;
    int mm_pioggia; 
} tipo_dati_file;

regioneэто неинициализированный указатель, который указывает на некоторую неопределенную область памяти (которой вы не владеете).Когда вы пытаетесь написать символы там с помощью sscanf (BOOM - SegFault - скорее всего).

У вас есть два варианта: (1) объявлять regione как фиксированный массив, например, char regione[CONST] (неэффективно)или (2) прочитайте строку для regione во временный буфер, а затем выделите память для strlen + 1 символов и скопируйте строку из временного буфера в новый блок памяти и назначьте начальный адресдля этого блока до regione (вы можете использовать strlen/malloc/memcpy или strdup - если он у вас есть, он делает все три)

С этим исправлением и несколькими изменениями для вашей функции crea_array (например, передав указатель на int для хранения номера структуры, заполненной в array вместо v), вы можете сделать что-то вроде следующего:

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

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

typedef enum mese_e { Genv= 1, Feb, Mar, Apr, Mag, Giu, 
                        Lug, Ago, Set, Ott, Nov, Dic
} tipo_mese; 

typedef struct data_s {
    int giorno;
    tipo_mese mese;
    int anno;
} tipo_data;

typedef struct dati_file_s {
    char* regione;
    tipo_data data;
    int mm_pioggia; 
} tipo_dati_file;

typedef struct ritorna_s {
    tipo_dati_file* array;
    int count; 
} tipo_ritorna;

tipo_dati_file *crea_array (char *Nome_f, int *nelem) 
{
    int i = 0,
        r;
    char region[MAXC] = ""; /* temp buffer to hold array[i].regione */
    char buf[MAXC] = "";
    FILE* file;

    tipo_dati_file *array = malloc (MAXDATA * sizeof *array);
    file = fopen (Nome_f, "r");

    if (file == NULL) {
        printf ("Errore in apertura del file!");
        return NULL;
    }

    if (!array) {   /* if you allocate, you must validate - every time */
        perror ("malloc-array");
        return NULL;
    }

    while (fgets (buf, MAXC, file) != NULL)
    {
        r = sscanf (buf, "%s %d/%d/%d %d", region, 
                    &array[i].data.giorno, (int*)&array[i].data.mese, 
                    &array[i].data.anno, &array[i].mm_pioggia);

        if (r != 5) {   /* validate return of every (s)scanf funciton */
            fputs ("error: failed to parse buf.\n", stderr);
            continue;   /* get next line */
        }

        array[i].regione = strdup (region);
        if (!array[i].regione) {  /* strdup allocates - you must validate */
            perror ("strdup-array[i].regione");
            for (int j = 0; j < i; j++)     /* on failure free prior mem */
                free (array[j].regione);    /* and return NULL */
            free (array);
            return NULL;
        }

        i++;
    }

    fclose (file);

    *nelem = i;     /* update nelem with number of struct filled */

    return array; 
}

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

    int index = 0,
        nelem = 0;
    char *datafile = argc > 1 ? argv[1] : "dat/staterain.txt";
    tipo_ritorna statistics[MAXDATA] = {{ .array = NULL }};

    statistics[index].array = crea_array (datafile, &nelem);

    if (statistics[index].array && nelem > 0) {
        statistics[index].count = nelem;
        for (int i = 0; i < statistics[index].count; i++) {
            printf ("%-12s   %02d/%02d/%4d   %3d\n", 
                    statistics[index].array[i].regione, 
                    statistics[index].array[i].data.giorno, 
                    statistics[index].array[i].data.mese, 
                    statistics[index].array[i].data.anno, 
                    statistics[index].array[i].mm_pioggia);
            free (statistics[index].array[i].regione);  /* free strings */
        }
        free (statistics[index].array); /* free array */
    }

    return 0;
}

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

$ ./bin/staterain
Texas          03/03/2015     1
California     06/02/2013     5
Utah           03/01/2014    10

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

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

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

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

$ valgrind ./bin/staterain
==3349== Memcheck, a memory error detector
==3349== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3349== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3349== Command: ./bin/staterain
==3349==
Texas          03/03/2015     1
California     06/02/2013     5
Utah           03/01/2014    10
==3349==
==3349== HEAP SUMMARY:
==3349==     in use at exit: 0 bytes in 0 blocks
==3349==   total heap usage: 5 allocs, 5 frees, 2,110 bytes allocated
==3349==
==3349== All heap blocks were freed -- no leaks are possible
==3349==
==3349== For counts of detected and suppressed errors, rerun with: -v
==3349== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

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