Search code examples
cstringfileprintfcorruption

Why is a Printf in a Function Preventing a Variable in a Struct Array from going Corrupt in Another Function?


I have got a function to read a file and attribute to a struct array multiple ints and strings, my fscanf format string looks like this: "%d.%d.%d.%d %d %60c%60c%d %8c".

And everything is OK if while I scan it and if I use a printf, but if I don't then the 2 last strings become corrupted at the end.

Since the file is gigantic and that is not the purpose of the function, I would like to take that printf out.

 typedef struct
 {
    int nacional;
    int regional;
    int distrital;
    int municipal;
  }id_geo;

  typedef struct
  {
    id_geo id_geo;
    long int cartao_cid;
    char nome_dono[61];
    char morada[61];
    int num_porta;
    char codigo_postal[9];
   }prop_id_dono;
prop_id_dono *ler_ficheiro(char *file_name, int num_linha) {
    prop_id_dono *info_geral;
    long int cartao_cid;
    char nome_ficheiro;
    char nome_dono[60];
    char morada[60];
    int num_porta;
    char codigo_postal[8]; 
    int nacional;
    int regional;
    int distrital;
    int municipal; 
    int i = 0;
    FILE *fp; 

    fp = fopen(file_name, "r");
    if (fp == NULL)
        printf("Peço desculpa, mas não foi possível abrir o ficheiro.");

    info_geral = (prop_id_dono *)malloc(sizeof(prop_id_dono) * num_linhas); 

    while (fscanf(fp, "%d.%d.%d.%d %ld %60c%60c%d %8c\n",
           &nacional, &regional, &distrital, &municipal,
           &cartao_cid, nome_dono, morada, &num_porta, codigo_postal) != EOF) {

        // Faz o scan ao ficheiro através de variáveis temporais, sendo que só 
        // depois é que atribui valores ao vetor que contém a informação geral
        info_geral[i].id_geo.nacional = nacional;
        info_geral[i].id_geo.regional = regional;
        info_geral[i].id_geo.distrital = distrital;
        info_geral[i].id_geo.municipal = municipal;
        info_geral[i].cartao_cid = cartao_cid;
        strcpy(info_geral[i].nome_dono, nome_dono + '\0');
        strcpy(info_geral[i].morada, morada + '\0');
        info_geral[i].num_porta = num_porta;
        strcpy(info_geral[i].codigo_postal, codigo_postal + '\0');

        //This is the magical printf
        printf("%d.%d.%d.%d %ld %s%s%d %s \n",
               info_geral[i].id_geo.nacional, info_geral[i].id_geo.regional,
               info_geral[i].id_geo.distrital, info_geral[i].id_geo.municipal,
               info_geral[i].cartao_cid, info_geral[i].nome_dono,
               info_geral[i].morada, info_geral[i].num_porta, 
               info_geral[i].codigo_postal);
        i++;    
    }
    fclose(fp);
    return info_geral;
}

The input is something like this, but it repeats 20,000 something times in the file 1.1.1.1 1234568 Name, 60 caracters in total(including the spaces)Adress(same thing with the spaces here)House Number Postal Code


Solution

  • You parse input fragments with %60c into character arrays of size 60.

    Assuming the conversion succeeds, these arrays will not contain proper C strings, and your attempt at concatenating a '\0' does not work in C.

    Here are ways to improve your program:

    • you should read lines into a local array for easier error reporting in case of conversion failures, and use sscanf() fo parsing.
    • you should make the destination arrays one byte longer and set a null terminator at the end. The arrays in the prop_id_dono structure should be made large enough too.
    • you should check the return value of sscanf and report conversion failures
    • you should stop scanning after num_linhas lines.
    • you should check for memory allocation failure.

    Here is a corrected version:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct {
        int nacional;
        int regional;
        int distrital;
        int municipal;
    } id_geo;
    
    typedef struct {
        id_geo id_geo;
        long int cartao_cid;
        char nome_dono[61];
        char morada[61];
        int num_porta;
        char codigo_postal[9];
    } prop_id_dono;
    
    prop_id_dono *ler_ficheiro(const char *file_name, int num_linha) {
        char buf[256];
        prop_id_dono *info_geral;
        long int cartao_cid;
        char nome_ficheiro;
        char nome_dono[61];
        char morada[61];
        int num_porta;
        char codigo_postal[9]; 
        int nacional;
        int regional;
        int distrital;
        int municipal; 
        int i = 0;
        FILE *fp; 
    
        fp = fopen(file_name, "r");
        if (fp == NULL) {
            printf("Peço desculpa, mas não foi possível abrir o ficheiro.");
            return NULL;
        }
        // use calloc to allocate an array initialized to all bits zero
        info_geral = (prop_id_dono *)calloc(num_linhas, sizeof(prop_id_dono));
        if (info_geral == NULL) {
            fclose(fp);
            return NULL;
        }
    
        while (i < num_linhas && fgets(buf, sizeof buf, fp) != NULL) {
            // Faz o scan ao ficheiro através de variáveis temporais, sendo que só 
            // depois é que atribui valores ao vetor que contém a informação geral
            if (sscanf(buf, "%d.%d.%d.%d %ld %60c%60c%d %8c",
                       &nacional, &regional, &distrital, &municipal,
                       &cartao_cid, nome_dono, morada, &num_porta, codigo_postal) != 9) {
                printf("parsing error: %s", buf);
                continue;
            }
            info_geral[i].id_geo.nacional = nacional;
            info_geral[i].id_geo.regional = regional;
            info_geral[i].id_geo.distrital = distrital;
            info_geral[i].id_geo.municipal = municipal;
            info_geral[i].cartao_cid = cartao_cid;
            nome_dono[60] = '\0';
            strcpy(info_geral[i].nome_dono, nome_dono);
            morada[60] = '\0';
            strcpy(info_geral[i].morada, morada);
            info_geral[i].num_porta = num_porta;
            codigo_postal[8] = '\0';
            strcpy(info_geral[i].codigo_postal, codigo_postal);
            i++;    
        }
        fclose(fp);
        // note that the caller does not receive the number of lines parsed.
        // passing the address of a int for this purpose if a good solution.
        return info_geral;
    }