从二进制文件读取结构导致错误的字符

Reading struct from a binary file results in wrong characters

我有一个程序把struct写入一个二进制文件然后读取,但是输入的数据和输出的数据不匹配,读取后有错误的字符。

这是我输入数据后的预期:

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

这是我得到的:

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

如何解决这个问题,使输出格式为可读字符? 我尝试更改结构元素大小,但在 16 之后增加并减少到 15 以下后,我遇到了额外的读取错误。

程序如下所示:

#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;

}

好吧,我知道我不应该给你代码,但它就在这里。 我重写了你的大部分代码,因为就像我在评论中所说的那样,我觉得你的函数一次做了太多事情。 我也没有注意从 fd 中“链式读取”许多用户结构,你可以做到。

基本上,你有这个结构:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>

#pragma GCC diagnostic ignored "-Wwrite-strings"

typedef struct user{
    char firstname[16];
    char lastname[16];
    char location[16];
    unsigned int id;
} user_s;

而且我希望能够干干净净,所以我给它写了一个constructor/destruction函数(比如open是fd的构造函数,close是他的析构函数)

#define USER_CONSTRUCTOR {.firstname[0] = '[=11=]',  \
                          .lastname[0] = '[=11=]',   \
                          .location[0] = '[=11=]',   \
                          .id = 0                }

void User_Constructor(user_s *self)
{
    user_s tmp = USER_CONSTRUCTOR;
    
    *self = tmp;
}

void User_Destructor(user_s *self)
{
    // This function is currently useless and will be optimized away by gcc in this current state
    // His only purpose is to be able to add stuff in struct user (like pointer) that will need to 
    // be take care at the end (freed ?) without looking everywhere in your code.
}

定义将允许我做类似

的事情
int main(void)
{
    user_s user = USER_CONSTRUCTOR;
    ....

现在,既然你想分配你的用户,我以同样的方式添加一个 New/Delete 函数:

user_s *User_New(void)
{
    user_s *new = NULL;
    
    if (!(new = malloc(sizeof(*new)))) {
        // TODO : printf error message
        return NULL;
    }
    User_Constructor(new);
    
    return new;
}

void User_Delete(user_s *self)
{
    if (self) {
        User_Destructor(self);
        free(self);
    }
}

现在,进入主要事件:您希望能够从 2 个来源填充您的用户结构:stdin 和 fd。

所以,您只需要这样做:

void User_Copy(user_s *dst, const user_s *src)
{
    strcpy(dst->firstname, src->firstname);
    strcpy(dst->lastname, src->lastname);
    strcpy(dst->location, src->location);
    dst->id = src->id;
}

bool User_InitialiseFromStdin(user_s *self)
{
    // TODO : You have to check every scanf return to be sure that you have correctly read the input ! 
    // TODO : And you have to clear the buffer if there are char in it after the input, or else the next scanf will use it !
    // TODO : And don't forget to prevent buffer-overflow ! that means limitating the number of char you can put into firstname, lastname, location !

    printf("Enter first name : ");
    fflush(stdout);
    if (scanf("%15s", self->firstname) != 1) {
        return false;
    }
    printf("Enter last name : ");
    fflush(stdout);
    if (scanf("%s15", self->lastname) != 1) {
        return false;
    }
    printf("Enter location : ");
    fflush(stdout);
    if (scanf("%15s", self->location) != 1) {
        return false;
    }
    printf("Enter ID : ");
    fflush(stdout);
    if (scanf("%u", &self->id) != 1) {
        return false;
    }
    
    return true;
}

bool User_InitialiseFromFileDescriptor(user_s *self, int fd)
{
    user_s  user           = USER_CONSTRUCTOR;
    ssize_t nbBytesRead    = 0;
    bool    returnFunction = false;
    
    if ((nbBytesRead = read(fd, &user, sizeof(user))) != sizeof(user)) {
        // TODO : Error message (you can use nbBytesRead and errno to have custom message, like you did)
        goto END_FUNCTION; 
    }
    // It's dangerous here, because we don't know if all char array (firstname, lastname, location) contain an ending [=14=]
    // TODO : Check it before copying the user struct
    User_Copy(self, &user);
    
    returnFunction = true;
    /* GOTO */END_FUNCTION:
    User_Destructor(&user);
    return returnFunction;
}

我希望你现在看到这些函数是多么容易阅读,因为它们只做一件事(函数的大部分是检查错误......)。

如果你想将结构写入 fd,也很简单:

bool User_WriteInfoFileDescriptor(const user_s *self, int fd)
{
    ssize_t nbBytesRead = 0;
    
    if ((nbBytesRead = write(fd, self, sizeof(*self))) != sizeof(*self)) {
        // TODO : Error message (you can use nbBytesRead and errno to have custom message)
        return false;
    }

    return true;
}

您实际上不需要做任何其他事情。请注意 firstname 是一个 15 个字母的字符串,它将包含 \0 之后的任何内容。因此,如果您使用文本编辑器打开 fd,您可能会看到“John[=48=]h%*$9as:rTDoe[=49=]G!:§$=IuyaA\Miami[=50=]Klm%%$: T”,如果您输入“无名氏迈阿密"。

现在,我们想看看我们的结构包含什么,所以一个基本的 Display 函数

void User_Display(const user_s *self, bool showHeader)
{
    if (showHeader) {
        printf("|First Name          | Last Name          | Location           | ID          |\n");
        printf("------------------------------------------------------------------------------\n");
    }
    printf("|%-20s|%-20s|%-20s|%-13u|\n", self->firstname, self->lastname, self->location, self->id);
    printf("------------------------------------------------------------------------------\n");
}

一个简单的主要测试已经出来了:

int main(void)
{
    user_s     user1   = USER_CONSTRUCTOR;
    user_s     user2   = USER_CONSTRUCTOR;
    int        fd      = -1;
    const char *fdPath = "data.txt";
    
    printf("\n");
    User_Display(&user1, true);
    User_Display(&user2, false);
    
    User_InitialiseFromStdin(&user1);
    
    printf("\n");
    User_Display(&user1, true);
    User_Display(&user2, false);
    
    fd = open(fdPath, O_CREAT | O_WRONLY);
    User_WriteInfoFileDescriptor(&user1, fd);
    close(fd);
    
    fd = open(fdPath, O_RDONLY);
    User_InitialiseFromFileDescriptor(&user2, fd);
    close(fd);
       
    printf("\n"); 
    User_Display(&user1, true);
    User_Display(&user2, false);
    
    User_Destructor(&user1);
    User_Destructor(&user2);
    return EXIT_SUCCESS;
}

现在,如果您想从 fd 中读取 X struct user,我建议您使用另一个结构来表示 struct user 的动态数组,并具有将用户添加到数组、删除它的功能,显示所有用户等

我希望这会有用吗?

我已经修复了你的代码。最大的错误是将数据写入您传递 &sp 的文件时,而 sp 已经是一个指针。

  • 我添加了一些错误检查。
  • data_read不要再关闭文件(以后用到)
  • 修复了所有警告(使用 MSVC2019 编译)。
  • openreadwriteclose 使用下划线前缀函数名称(如果您的编译器不喜欢,请删除下划线)。
  • 释放分配的内存。

你还有很大的进步空间。

#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;
}