Rewind File, Create Dynamic Struct

2019-09-01 06:48发布

问题:

this function will rewind file, create the dynamic array (of size), and read in the data, populating the _data struct dynamic array. Note that stream is passed by value this time. The function then returns the populated array of struct

struct _data
{
    char* name;
    long number;
};

struct _data *load(FILE *stream, int size)
{
    struct _data BlackBox = calloc(size, sizeof(_data));

    char tempName[3];

    stream = fopen("names.txt", "r");
    for (int i=0; i<size; i++)
    {
        fscanf(stream, "%s %ld", tempName, &data.number);
        BlackBox[i].name = calloc(strlen(tempName), sizeof(char));
        strcpy(BlackBox[i].name, tempName);
    }
    fclose(stream);

    return &BlackBox;
}

File Content
ron 7774013
jon 7774014

I am a beginner and having difficulty designing the code. Can someone please explain. Thanks

回答1:

I think you have some warning from gcc to help you.

Fix memory management with your calloc, and don't return a stack pointer

typedef struct _data                                                           
{                                                                              
    char* name;                                                                
    long number;                                                               
} _data;                                                                       

_data *load(FILE *stream, int size)                                            
{                                                                              
    _data *BlackBox = calloc(size, sizeof(_data));                             

    char tempName[3];                                                          

    for (int i=0; i<size; i++)                                                 
    {                                                                          
        fscanf(stream, "%s %ld", tempName, &BlackBox[i].number);               
        BlackBox[i].name = strdup(tempName);                                   
    }                                                                          
    fclose(stream);                                                            

    return BlackBox;                                                           
}                                                                              

int main (void)                                                                
{                                                                              
    FILE *f = fopen("test.data", "r");                                         
    _data *data = load(f, 2);                                                  
    printf("%s %ld\n", data[0].name, data[0].number);                          
    printf("%s %ld\n", data[1].name, data[1].number);                          
    return 0;                                                                  
}   

output

 aurel@vm-pontarlier:~$ ./a.out 
ron 7774013
jon 7774014

Think about change _data

typedef struct _data{
    char name[256];
    long number;
} _data;

scan will be:

for (int i=0; i<size; i++)                                                 
{                                                                          
    fscanf(stream, "%s %ld", BlackBox[i].name, &BlackBox[i].number);       
}   


回答2:

You are making the same arrors as with your previous post. You also don't allocate memory for the name member in _data. As for the compilation errors:

  • Arrays of type T that you allocate dynamically with malloc or calloc are controlled by a handle, a pointer to T of type T*.
  • The structure you defined is of type struct _data, the type includes the keyword struct. If you want a one-word identifier for your type, use `typedef´ as Ôrel has shown you.
  • You have no variable called ´data. The handle to the newly allocated memory is ´BlackBox.

If you fix these errors, you will run into logical errors that the compiler cannot know about:

  • Your BlackBox[i].name is initialised to NULL via calloc, so it doesn't point to valid memory. You cannot strcpy anything into it. What you can do is to use the non-standard, but widely available strdup, which first allocates memory as required and then copies.
  • Your temporary string is of length 3; it can hold at most two characters plus the null terminator. The names in your file are short, granted, but your program must prepare for any input, even illegal input.
  • The FILE * isn't used outside your function; it should be local to load.
  • The user must specify how many items to read. This defies the purpose of dynamic allocation. Also the user can't know how many items were read; there might be fewer items in the file as requested. Your function design should reflect that. (Okay, thst's not really true: You zero-initialise the memory, which means a char pointer of NULL signals the end of the array, but:)
  • You don't test the output of fscanf, which you should. If your file has fewer than size items, the last items will contain garbage, because you copy garbage values into the zero initialised memory.
  • Not an error per se, but if your file is organised along lines, consider reading a line with fgets first and then parsing that with sscanf.

The example code below tries to incorporate these guidelines. Things to note:

  • The memory isn't allocated in one huge chunk, but it grows as needed with subsequent calls to realloc. The call realloc(NULL, s) is equivalent to malloc(s).
  • The functions "fills in" the number of elementsread via a pointer to an integer.
  • The strings are copied into newly allocated memory. That means that in theory, the strings can e as long as they want. (In practice, the max buffer length of 20 cuts such strings short.) The function duplicate emulates the function strdup.
  • These strings should be freed when cleaning up.
  • The load function takes a filename istead of a file handle.
  • File reading takes place in two stages: Read a line first, then scan this line. If the format is not "string without spaces" + "number", an error message is written.

Anyway, here goes:

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

struct _data
{
    char *name;
    long number;
};

/*
 *      Duplicate a string on the heap (aka strdup)
 */
char *duplicate(const char *str)
{
    char *p = malloc(strlen(str) + 1);

    strcpy(p, str);
    return p;
}

/*
 *      Read a list of names from file fn
 */
struct _data *load(const char *fn, int *psize)
{
    struct _data *data = NULL;
    FILE *stream;
    char line[80];      // buffer for line
    int lnr = 0;        // line number for error message
    int n = 0;          // number of read items

    stream = fopen("names.txt", "r");
    if (stream == NULL) return NULL;    // Can't open file

    while (fgets(line, sizeof(line), stream)) {
        long int number;
        char buf[20];

        lnr++;
        if (sscanf(line, "%19s %ld", buf, &number) == 2) {
            data = realloc(data, (n + 1) * sizeof(*data));
            data[n].number = number;
            data[n].name = duplicate(buf);
            n++;
        } else {
            fprintf(stderr, "[%s, line %d] Illegal format\n", fn, lnr);
        }
    }
    fclose(stream);
    *psize = n;             // Assign number of read items

    return data;
}

/*
 *      Free memory allocated by load
 */
void cleanup(struct _data *data, int n)
{
    while (n--) free(data[n].name);
    free(data);
}

int main()
{
    struct _data *data;
    int i, n;

    data = load("names.txt", &n);
    if (data == NULL) return -1;

    for (i = 0; i < n; i++) {
        printf("%-20s%12ld\n", data[i].name, data[i].number);
    }

    cleanup(data, n);

    return 0;
}


回答3:

...and lastly tempName[3] is too small: it must be at least 4, given your 3 letter inputs. Further, you forget to allocate room for the terminating null character in your malloc call:

char tempName[4];
...
BlackBox[i].name = malloc(strlen(tempName)+1);

(That tempName[3] did not lead to errors is because the compiler probably rounded it up to an even number of bytes - but it is a typical beginner's fault.)