Search code examples
cstructbinaryfiles

Reading struct from a binary file results in wrong characters


I have a program that writes struct to a binary file and then reads it, but the entered data does not match with output data, after reading it, there are wrong characters.

This is what I expected after entering data:

|First Name          | Last Name           | Location           | ID          |
--------------------------------------------------------------
John                   Doe                   London               1

This is what I got:

|First Name          | Last Name           | Location           | ID          |
--------------------------------------------------------------
└$↔                 | îQ@                 | á¡ K∙⌂    | 1385584

How can this be fixed, so that the output is formatted in readable characters? I tried changing the struct element sizes but after increasing after 16 and reducing below 15, i got additional read errors.

The program looks like this:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#pragma GCC diagnostic ignored "-Wwrite-strings"
struct user{
    char firstname[16], lastname[16], location[16];
    unsigned int id;};

struct user * data_entry(){
    struct user *data = (struct user*) malloc(sizeof(struct user));
    printf("Enter first name:");
    scanf("%s", data->firstname);
    printf("Enter last name:");
    scanf("%s", data->lastname);
    printf("Enter location:");
    scanf("%s", data->location);
    printf("Enter ID:");
    scanf("%d", &data->id);
    return data;}

int data_read(int bytes, int fd, const char *file){
    struct user *data = NULL;
    struct user *tmp;
    int idx = 0;
    int count = 0;
    data = (struct user *)malloc(sizeof(struct user));

    while (bytes > 0) {
      bytes = read(fd, data + count, sizeof(struct user));
      if (bytes == -1){
        printf("File read error: %i (%s)\n",  errno, strerror(errno));
        close(fd);
        free(data);
        return errno;}

      if (!bytes){
        break;}

      if (bytes < sizeof(struct user)){
        printf("Error: read %i bytes out of %llu\n", bytes, sizeof(struct user));
        close(fd);
        free(data);
        return errno;}

        count++;
        tmp = (struct user *)realloc(data, sizeof(struct user) * (count + 1));

        if (!tmp){
          close(fd);
          free(data);
          return errno;}
        data = tmp;
  }
    printf("|First Name          |"
        " Last Name           |"
        " Location           |"
      " ID          |\n");
    printf("--------------------------------------------------------------\n");
    while (count--){
        printf("%-20s| %-20s| %-10s| %-7d\n",
                (data + idx)->firstname,
                (data + idx)->lastname,
                (data + idx)->location,
                &(data + idx)->id);
        idx++;}
    printf("\n[!] %i records from %s have been read\n", idx, file);
    printf("-------------------------------------------\n");
return 0;
}

int main (){
    int bytes = 1;
    int fd;
    int num;
    struct user *sp;
    const char *file = "data.bin";
    struct user *tmp;

    fd = open(file, O_CREAT | O_APPEND | O_RDWR, 0000777);

    if (fd == -1){
      printf("File open error: %i (%s)\n", errno, strerror(errno));
        return fd;}
    data_read(bytes, fd, file);

    sp = (struct user *)malloc(sizeof(struct user));
    if (!sp){
        printf("Memory allocation has failed\n");
        return -1;
    }
    sp = data_entry();
    bytes = write(fd, &sp, sizeof(struct user));
    if (bytes == -1) {
        printf("File write error: %i (%s)\n", errno, strerror(errno));
        close(fd);
        return bytes;}

    printf("User data saved to %u (%i bytes)\n", file, bytes);
    free(sp);
    close(fd);

    return 0;

}

Solution

  • I have fixed your code. The biggest error was when writing data to the file where you passed &sp while spis already a pointer.

    • I added some error checking.
    • data_read do not close the file anymore (used later).
    • Fixed all warnings (Compiled with MSVC2019).
    • Used underscore prefixed function names for open, read, write and close (Remove underscore if your compiler doesn't like that).
    • Freed allocated memory.

    Thee is still plenty of room for improvement.

    #include <stdio.h>
    #include <fcntl.h>
    #include <io.h>           // MSVC2019 want that
    //#include <unistd.h>     // MSVC2019 doesn't know it
    #include <string.h>
    #include <errno.h>
    #include <stdlib.h>
    
    struct user {
        char firstname[16], lastname[16], location[16];
        unsigned int id;
    };
    
    struct user* data_entry() {
        struct user* data = malloc(sizeof(struct user));
        if (data != NULL) {
            printf("Enter first name:");
            scanf("%s", data->firstname);
            printf("Enter last name:");
            scanf("%s", data->lastname);
            printf("Enter location:");
            scanf("%s", data->location);
            printf("Enter ID:");
            scanf("%d", &data->id);
        }
        return data;
    }
    
    int data_read(int fd, const char* fileName) {
        int bytes;
        struct user* data = NULL;
        struct user* tmp;
        int idx = 0;
        int count = 0;
        data = (struct user*)malloc(sizeof(struct user));
        if (data == NULL) {
            printf("Memory allocation error\n");
            return errno;
        }
    
        while (1) {
            bytes = _read(fd, data + count, sizeof(struct user));
            if (bytes == 0)  // End of file
                break;
            if (bytes == -1) {
                printf("File read error: %i (%s)\n", errno, strerror(errno));
                free(data);
                return errno;
            }
    
            if (bytes != sizeof(struct user)) {
                printf("Error: read %i bytes out of %d\n", bytes, sizeof(struct user));
                free(data);
                return errno;
            }
    
            count++;
            tmp = (struct user*)realloc(data, sizeof(struct user) * (count + 1));
    
            if (!tmp) {
                free(data);
                return errno;
            }
            data = tmp;
        }
        printf("|First Name          |"
            " Last Name           |"
            " Location           |"
            " ID          |\n");
        printf("--------------------------------------------------------------\n");
        while (count--) {
            printf("%-20s| %-20s| %-10s| %-7u\n",
                (data + idx)->firstname,
                (data + idx)->lastname,
                (data + idx)->location,
                (data + idx)->id);
            idx++;
        }
        printf("\n[!] %i records from %s have been read\n", idx, fileName);
        printf("-------------------------------------------\n");
        free(data);
        return 0;
    }
    
    int main() {
        int bytes;
        int fd;
        struct user* sp;
        const char* file = "data.bin";
    
        fd = _open(file, O_CREAT | O_APPEND | O_RDWR, 0000777);
        if (fd == -1) {
            printf("File open error: %i (%s)\n", errno, strerror(errno));
            return 1;
        }
    
        data_read(fd, file);
    
        sp = data_entry();
        if (sp == NULL) {
            printf("data_entry failed\n");
            _close(fd);
            return 1;
        }
    
        bytes = _write(fd, sp, sizeof(struct user));
        if (bytes != sizeof(struct user)) {
            if (bytes == -1)
                printf("File write error: %i (%s)\n", errno, strerror(errno));
            else
                printf("Not all bytes written to file\n");
            _close(fd);
            return 1;
        }
    
        printf("User data saved to %s (%i bytes)\n", file, bytes);
        free(sp);
        _close(fd);
    
        return 0;
    }