I. S.
I. S.

Reputation: 103

How to dynamically allocate struct fields based on data read from binary file?

I have a structure that looks like this :

typedef struct Student {    //dynamically allocated structure
    char *first_name;
    char *last_name;
    float grade;
}Student;

What I want to do is to read data from a binary file and dynamically allocate memory for the first_name and last_name fields. I successfully completed this task when reading from a normal .txt file. Here is the code:

void Read_From_File(const char *file_name, Student *p_destination, const int number_of_students)
{
    FILE *p_file = fopen(file_name, "r");
    if (!p_file)
    {
        perror("Error opening the file. \n");
        exit(EXIT_FAILURE);
    }
    int index = 0;
    while (index < number_of_students)
    {
        unsigned char buffer[256];
        fscanf (p_file, "%s", buffer);
        p_destination[index].last_name = (char*)malloc(strlen(buffer) + 1);
        strcpy(p_destination[index].last_name, buffer);

        fscanf(p_file, "%s", buffer);
        p_destination[index].first_name = (char*)malloc(strlen(buffer) + 1);
        strcpy(p_destination[index].first_name, buffer);

        fscanf(p_file, "%f", &p_destination[index].grade);
        index++;
    }
    fclose(p_file);
}

It was simple because fscanf can read data individually and has type specifiers, like the following : %s last name / %s first name / %f grade. I don't know how to use fread to read single pieces of data like I did with fscanf. Here is what I've tried so far, but it doesn't work:

int number_of_students = 0;
unsigned char buffer[256];
char file_name[10];
FILE *p_file = NULL;
Student *p_students = NULL;

printf("Enter file name. \n");
scanf("%s", file_name);

p_file = fopen(file_name, "rb");

fread(&number_of_students, sizeof(number_of_students), 1, p_file);
p_students = (Student *)malloc(number_of_students * sizeof(Student));
if (!p_students)
{
    perror("Could not allocate memory. \n");
    exit(EXIT_FAILURE);
}

for (int index = 0; index < number_of_students; ++index)
{
    fread(buffer, sizeof(char), 1, p_file);
    p_students[index].last_name = (char *)malloc(strlen(buffer) + 1);
    if (!p_students[index].last_name)
    {
        printf("Could not allocate memory. \n");
        exit(EXIT_FAILURE);
    }
    strcpy(p_students[index].last_name, buffer);

    fread(buffer, sizeof(char), 1, p_file);
    p_students[index].first_name = (char *)malloc(strlen(buffer) + 1);
    if (!p_students[index].first_name)
    {
        printf("Could not allocate memory. \n");
        exit(EXIT_FAILURE);
    }
    strcpy(p_students[index].first_name, buffer);

    fread(&p_students[index].grade, sizeof(float), 1, p_file);
}
Print_Students(p_students, number_of_students);

Also, the Print_Students function looks like this:

void Print_Students(Student *p_students, int number_of_students)
{
    printf("================================================================================================\n");
    for (unsigned int index = 0; index < number_of_students; ++index)
    {
        printf("\t\t===================== Student %d =====================\n\n", index);
        printf("Last name: %s\n", p_students[index].last_name);
        printf("First name: %s\n", p_students[index].first_name);
        printf("Grade: %.2f\n", p_students[index].grade);
    }
    printf("\n");
}

The input.txt file looks like this:

19
Terresa Minaya 9.50
Otto Pleiman 7 
Illa Holzman 5.25
Alona Greeson 3.40
Natalya Vickrey 10
Catrina Cho 9.34
Loida Dinapoli 9.43
Neely Mcelligott 8.30
Salome Urban 8.75
Rosetta Dagenhart 9.10
Diane Cooksey 10
Novella Longmire 3
Gilberte Manganaro 4
Joye Humbert 5
Justa Larock 6
Delana Bufkin 7
Genaro Kenison 8.98
Refugio Arena 4.56
Iona Nida 7.65

I loaded the data from this file in my vector of Students, and then I wrote the input.bin file using based on the data from my vector. I've used the following code to write to binary:

void Write_To_Binary(const char *file_name, Student *p_source, const int number_of_students)
{   
    size_t successfully_written = 0;
    FILE *p_file = fopen(file_name, "wb");
    if (!p_file)
    {
        perror("Error opening the file. \n");
        exit(EXIT_FAILURE);
    }
    successfully_written = fwrite(&number_of_students, sizeof(number_of_students), 1, p_file);
    if (successfully_written != 1)
    {
        perror("Error writing to the file. \n");
        exit(EXIT_FAILURE);
    }
    successfully_written = fwrite(p_source, sizeof(Student), number_of_students, p_file);
    if (successfully_written != number_of_students)
    {
        perror("Error writing all the student data. \n");
        exit(EXIT_FAILURE);
    }
    fclose(p_file);
}

UPDATE: I've followed iBug's solution and it worked. I changed the data fields as it follows: sizeof(last_name) last_name sizeof(first_name) first_name grade.

I'll post here the functions I've used to write to the bin file, and read from it.

Here is the function Write_To_Binary

    void Write_To_Binary(const char *file_name, Student *p_source, const unsigned int number_of_students)
{
    FILE *p_file = fopen(file_name, "wb");
    if (!p_file)
    {
        perror("Error opening the file. \n");
        exit(EXIT_FAILURE);
    }

    size_t successfully_written = 0;
    successfully_written = fwrite(&number_of_students, sizeof(number_of_students), 1, p_file);
    if (successfully_written != 1)
    {
        printf("Error writing to the file. \n");
        exit(EXIT_FAILURE);
    }

    unsigned int width = 0, width1 = 0;
    for (unsigned int index = 0; index < number_of_students; ++index)
    {
        width = strlen(p_source[index].last_name) + 1;

        successfully_written = fwrite(&width, sizeof(width), 1, p_file);
        printf("Name: %s Width: %d \n", p_source[index].last_name, width);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_written = fwrite(p_source[index].last_name, width, 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        width = strlen(p_source[index].first_name) + 1;

        successfully_written = fwrite(&width, sizeof(width), 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_written = fwrite(p_source[index].first_name, width, 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_written = fwrite(&p_source[index].grade, sizeof(float), 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }
    }

    fclose(p_file);
}

And here is the function Read_From_Binary

void Read_From_Binary(const char *file_name, Student *p_destination)
{
    FILE *p_file = fopen(file_name, "rb");
    if (!p_file)
    {
        perror("Error opening file. \n");
        exit(EXIT_FAILURE);
    }

    unsigned int number_of_students = 0;
    size_t successfully_read;
    successfully_read = fread(&number_of_students, sizeof(number_of_students), 1, p_file);
    if (successfully_read != 1)
    {
        printf("Error reading the number of students. \n");
        exit(EXIT_FAILURE);
    }

    unsigned int width = 0;
    for (unsigned int index = 0; index < number_of_students; ++index)
    {
        successfully_read = fread(&width, sizeof(width), 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        p_destination[index].last_name = (char*)malloc(width);
        if (!p_destination[index].last_name)
        {
            printf("Could not allocate memory. \n");
            exit(EXIT_FAILURE);
        }
        successfully_read = fread(p_destination[index].last_name, width, 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_read = fread(&width, sizeof(width), 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        p_destination[index].first_name = (char*)malloc(width);
        if (!p_destination[index].first_name)
        {
            printf("Could not allocate memory. \n");
            exit(EXIT_FAILURE);
        }
        successfully_read = fread(p_destination[index].first_name, width, 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_read = fread(&p_destination[index].grade, sizeof(float), 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }
    }

    fclose(p_file);
}

Upvotes: 0

Views: 454

Answers (2)

Paul Ogilvie
Paul Ogilvie

Reputation: 25286

You need to know the format of the file. I assume the following:

  • the file starts with a four-byte integer that holds the number of students in the file.

  • The data about each student is in the order First name, Last name, grade.

  • The strings first and last name are terminated with a null byte.

  • the float is a 4 byte float.

  • Following the 4 byte float follows the next student (there is no record demarcation).

Now you can read the data. The only thing is how to determine the length of the strings as you have to allocate memory for them. There are two ways:

  • use a buffer of reasonable size (say buf[256]) and read character by character until you see the null character (you may want to skip characters that would let the buffer overflow, so another part of the file format specification is that strings cannot be longer than 255). After reading and terminating the buffer with \0 call strlen and then allocate the string with malloc, or

  • remember the current position, read character by character, counting them until you see the \0, allocate the memory, seek backwards and read the string.

Upvotes: 1

iBug
iBug

Reputation: 37287

I did a similar job in a recent project.

I implemented a binary file format on my own, and it's structure is

MAGIC
number of records
record 1
    (int) length of this record
    data
record 2
    (int) length of this record
    data

So you can pit an extra 4 bytes (or an integer) indicating the length of what to read, and read according to that number.

Advantages of this implementation are:

  • You don't need another buffer to read first, which may add an extra limitation on lengths of records.
  • When the content format changes, you don't have to worry about reading lengths.
  • No single character needs to be used as "terminator" because the length is pre-determined.

Upvotes: 1

Related Questions