从二进制文件读取结构导致错误的字符
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 编译)。
- 为
open
、read
、write
和 close
使用下划线前缀函数名称(如果您的编译器不喜欢,请删除下划线)。
- 释放分配的内存。
你还有很大的进步空间。
#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;
}
我有一个程序把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 编译)。
- 为
open
、read
、write
和close
使用下划线前缀函数名称(如果您的编译器不喜欢,请删除下划线)。 - 释放分配的内存。
你还有很大的进步空间。
#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;
}