Reading bytes of a file into a structure - c

How would I go about reading a binary file and assigning their values to a structure? Each structure with its content will be written to a csv file.
I have this data file, it's a list of struct product entries.
Here is the Product structure in an h file:
struct product {
char code[15];
char name[50];
short int quantity;
double price;
}
typedef struct product *Product;
And here is how I'm trying to read each line into the Product structure and writing it to the csv file:
File *fp;
File *outFile;
fp = fopen("products.dat", "rb");
outFile = fopen("allproducts.csv", "w");
Product p;
while (fread(p, sizeof(Product), 1, fp) == 1) {
fwrite(p->code, sizeof(p->code), 1, outFile);
}
I don't think I'm reading the file correctly because when I try writing to the file, all I get are the question marks in a box in the csv file.

If the writer of the data file does not take care for the size of the struct it writes, the reader has trouble to make sense of it. I played around with it a bit and found out that a small adjustment is needed to get the proper result on my machine and OS.
#include <stdio.h>
#include <stdlib.h>
#define ADJUSTMENT 4
struct product {
char code[15];
char name[50];
short int quantity;
double price;
};
int main(void)
{
FILE *fp;
//FILE *outFile;
struct product p;
fp = fopen("products.dat", "rb");
if (fp == NULL) {
fprintf(stderr, "fopen failed\n");
exit(EXIT_FAILURE);
}
//outFile = fopen("allproducts.csv", "w");
while (fread(&p, sizeof(p) - ADJUSTMENT, 1, fp) == 1) {
printf("CODE: %s\n", p.code);
}
exit(EXIT_SUCCESS);
}
It is a well known problem with such simple attempts for writing binary data, it is just not portable and I'm pretty sure that the value of my adjustment does not fit for you. You need to find a method to make it portable.

I think your below code needs to be modified.
Your Original Code:
File *fp;
File *outFile;
fp = fopen("products.dat", "rb");
outFile = fopen("allproducts.csv", "w");
Product p;
while (fread(p, sizeof(Product), 1, fp) == 1) {
fwrite(p->code, sizeof(p->code), 1, outFile);
}
Modified Code:
File *fp;
File *outFile;
fp = fopen("products.dat", "rb");
outFile = fopen("allproducts.csv", "w");
Product p = malloc(sizeof(struct product));
while (fread(p, sizeof(struct product), 1, fp) == 1) {
fwrite(p->code, sizeof(p->code), 1, outFile);
}
Problems are:
p is a pointer to struct as it is of type Product which is a pointer to struct, you need to allocate memory for it before you can use it
In fread you are passing sizeof(Product) which essentially passes the size of the pointer, whereas you want to read the sizeof the structure product.
The other problems which I haven't corrected is checking the return values of fopen,malloc etc which I leave it to you. : )

Hiding pointers behind typedefs is error prone and leads to confusing code.
p is defined as a pointer to a struct product, but is not initialized: passing its value to fread as the destination invokes undefined behavior.
sizeof(Product) is the size of the pointer, not the size of the structure.
Do not define Product as a pointer, make it a typedef for the structure if you can, or do not use the typedef if you cannot:
struct product {
char code[15];
char name[50];
short int quantity;
double price;
};
typedef struct product Product;
Adjust the code and write the string to the csv file, not the complete array of chars:
File *fp;
File *outFile;
fp = fopen("products.dat", "rb");
outFile = fopen("allproducts.csv", "w");
struct product prod;
while (fread(&prod, sizeof(prod), 1, fp) == 1) {
fprintf(outFile, "%s\n", prod.code);
}
Note that this will be incorrect if the product code contains embedded spaces, ,, " or newline characters.

Related

How do I use fgets so that it reads lines from a file and stores it in an array?

I'm trying to make two functions, a writefile and a readfile. Obviously, writefile will write to a file using the contents from the struct pokemon, and readfile will read the file line by line and get the contents of the file. For example, after writing to a file successfully with two pokemon in the struct, the text file will look like this:
30
Pikachu
12
Charizard
The read file is supposed to store the level and name in the struct, and I'm currently having trouble. I've already been told that using fgets is correct, but I'm not doing it correctly.
struct pokemon
{
int level;
char name[30];
};
int readfile(struct pokemon pokearray[], int* num, char filename[])
{
FILE * fp;
int i = 0;
fp = fopen(filename, "r");
pokearray[10].level;
if (fp == NULL)
{
printf("errorrr");
return -1;
}
while (!feof(fp))
{
fgets(pokearray[i].level, 10, fp);
}
fclose(fp);
return 0;
}

How to store words from file line with separators into a struct

I have an input file named cstats.txt that only has one line:
name:age:phone:city
And I have a struct Citizen:
typedef struct {
char name[100];
int age;
char city[100]
char phone[10];
} Citizen;
I need to read from the file and save the attributes in the struct:
I tried:
Citizen a;
fread(a, sizeof(Citizen), 1, "cstats.txt");
But I think it isn't saving the attributes.
I searched here on SO but all I found was to read line by line.
Thank you!
fread is not the best(simplest) tool for this job, use fgets instead.
Read the whole line, after that parse the fields with something like sscanf, example:
char line[250];
Citizen a;
FILE *file = fopen("cstats.txt", "r");
if (file != NULL)
{
if (fgets(line, sizeof line, file))
{
sscanf(line, "%99[^:]:%d:%9[^:]:%99[^\n]", a.name, &a.age, a.phone, a.city);
}
}
About fread, a good use case for it is to read data from a file that was writen to by fwrite, that would guarantee that the data would be appropriately formated to be read directly to a buffer like the one used to write, for instance:
Citizen a;
Citizen b;
FILE *file = fopen("file.txt", "w+"); // open to read and write
fwrite(&a, sizeof a, 1, file); // write Citizen a to file
fseek(file, 0, SEEK_SET); // reset file position indicator
// rewind(file); // alternative
fread(&b, sizeof b, 1, file); // read data directly to Citizen b
In the above code I skipped return value checking for simplicity purposes, you should do it in your code.
Live demo

How to make my program increase the price of the book (percent) depending on user inputs?

I've made a program that reads a file named "books.txt" and displays its contents. In the file, there is a price for each of the 4 books.
I'm not sure how to make my program increase the price of the book (percent) depending on user inputs; for example, the user is prompted to enter a number and that number (which is a percentage) is multiplied by the price of each book and gives the updated price.
How do I do it?
Here's my code:
int main() {
/* File pointer to hold reference to our file */
FILE * fPtr;
char buffer[BUFFER_SIZE];
int totalRead = 0;
/*
*/
fPtr = fopen("books.txt", "r");
if (fPtr == NULL) {
printf("Unable to open file.\n");
printf("Please check whether file exists and you have read privilege.\n");
exit(EXIT_FAILURE);
}
printf("File opened successfully. Reading file contents line by line. \n\n");
while (fgets(buffer, BUFFER_SIZE, fPtr) != NULL) {
totalRead = strlen(buffer);
/*
*/
buffer[totalRead - 1] = buffer[totalRead - 1] == '\n'
? '\0'
: buffer[totalRead - 1];
printf("%s\n", buffer);
}
fclose(fPtr);
return 0;
}
You can read the file and store all the book data in some array of struct such as
typedef struct {
int id;
char *title;
char *author;
int year;
float price;
}Book;
Then ask for the percentage to the user and re-write the file calculating the new price for each Book (price += percentage/100)
To re-write the file you need to open the file with "w permits. You can check for documentation here. Don't forget to close fPtr before opening the same file again with write permits

How to determine size and allocate memory for struct array c programming

Is there a way to determine how many elements an array of struct should have or allocate dynamic memory for a struct array in C? The code opens a binary file and reads it into a struct array. I can place an arbitrary value like 3000, however when I go to print it gives me the garbage values after the file ends. I'm looking for a way to set my array to the size of the file that is being read or limit it to the useful data. I imagine a function like malloc() just haven't been able to find examples with struct arrays that aren't static.
#include "function.h"
int main() {
FILE *fp; // file pointer
STRUCTNAME structVar[]; //typdef array of structs
fp = fopen("file.bin", "rb"); //binary file to read into array
if(fp == NULL) { // error check file opening
printf("error\n");
} else {
//while !feof to read file into array
while(!feof(fp)) {
fread(structVar, sizeof(structVar), 1, fp);
printStructData(structVar);
}
fclose(fp); // closes file
}
return 0;
}
STRUCTNAME *structVar;
FILE* fp = fopen(...);
if(fp)
{
long fileSize;
fseek(fp, 0 , SEEK_END);
fileSize = ftell(fp);
fseek(fp, 0 , SEEK_SET);
structVar = malloc(sizeof(STRUCTNAME) * (fileSize / sizeof(STRUCTNAME) + 1)); //just in case if the file length is not exactly sizeof(STRUCTNAME) * n
...
fclose(fp);
}

Why does the program only save some, but not all, of data to binary file?

After write data to the binary file, I changed the mode to "r" to read the file. The name of who is correct, but color and education are empty. Age is returned as a large integer number, which is I guess the address of the variable. So, what is wrong here?
Update: The answer of Retired Ninja and Thornkey almost solve my problem. The rest is if input of age is 26, but not other numbers, the program will not write correct input to file. Anyone know what is wrong here?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXLEN 100
typedef struct Person{
char name[MAXLEN];
int age;
char color[MAXLEN];
char education[MAXLEN];
} Person;
void create_person(Person *who){
printf("name: ");
fscanf(stdin, "%s", who->name);
printf("age: ");
fscanf(stdin, "%d", &(who->age));
printf("color: ");
fscanf(stdin, "%s", who->color);
printf("education: ");
fscanf(stdin, "%s", who->education);
}
void print_record(Person *who){
printf("name: %s\n", who->name);
printf("age: %d\n", who->age);
printf("color: %s\n", who->color);
printf("education: %s\n", who->education);
}
void load_db(FILE *fp, Person *who){
int result = fread(who, sizeof(who), 1, fp);
if(!result)
//printf("result%d", result);
printf("cannot load database");
}
FILE *connect_db (char *file_name, char *mode, Person *who){
FILE *fp = (FILE *)malloc(sizeof(100));
// open stream and load database from the file
if(strcmp(mode, "w") == 0){
fp = fopen(file_name, mode);
//load_db(conn); // load data from file
}else if(strcmp(mode, "r") == 0){
fp = fopen (file_name, mode);
load_db(fp, who); // load data from file
}else{
printf("incorrect mode");
}
return fp;
}
// save database to file
int save_db (FILE *fp, Person *who){
int result = fwrite(who, sizeof(who), 1, fp);
if(result){
return 0; // successfully save db
}
printf("cannot save db");
}
int main(int argc, char *argv[])
{
char answer[MAXLEN];
Person person;
Person *who = &person;
FILE *fp;
create_person(who);
fp = connect_db("record2.dat", "w", who);
save_db(fp, who);
print_record(who);
free(fp);
fclose(fp);
return 0;
}
fread() reads individual bytes. You want to read in numbers which you have printf'd.
Your files will look like this:
name: Samuel Thornkey
age: 24
colour: blue
education: PHD in computer science
But when you use fread, the program directly reads bytes from the file and fills them into the record. Your person will then contain:
char name[MAXLEN]: first MAXLEN characters i.e. "name: Samuel Thornkey\n age: 24\ncolour: blue\n" or something similar
int age: the rest of the characters, encoded as bytes, hence very large number
char color[MAXLEN]: empty
char education[MAXLEN]: empty
Instead, use fscanf:
fscanf(fp,"name:%s ",&who->name);
fscanf(fp,"age:%d ",&who->age);
and so on.
It's likely to be this:
free(fp);
fclose(fp);
You are not permitted to free memory that wasn't given to you by malloc (or realloc).
And, yes, you may think you've allocated it inside connect_db but (1) that's totally unnecessary, and (2) you overwrite the pointer when you call fopen.
In addition, save_db is using the size of the who pointer which will most likely not be the same as the type it points to.
So, make the following changes:
get rid of the call to malloc, just use FILE *fp; within connect_db.
get rid of the free(fp) within main.
in save_db, use sizeof(Person) rather than sizeof(who).
Here's a fixed version that may be of some help to you. It successfully fills in a Person, prints it, and writes it to the file. Then it reads the data back from the file into a different Person and prints it.
The structure is similar to what you had, but I made all the reading/writing explicit so you'd see the steps. In general it isn't a good idea to have functions that perform extra duties you might not always want. I also made create_person require no input for faster testing. Your input code looked okay, I just didn't want to type it every time.
One thing to keep in mind, if you plan to write binary data to a file you should open the file in binary mode ("wb" or "rb") to avoid line ending translation on systems that perform that on text files.
You might also consider that a file written on one system may not be readable on a different system if the size of the Person structure changes due to different alignment or int being a different size. Probably not an issue for you, but if it becomes one you might look into a different serialization scheme.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXLEN 100
typedef struct Person
{
char name[MAXLEN];
int age;
char color[MAXLEN];
char education[MAXLEN];
} Person;
void create_person(Person *who)
{
strcpy(who->name, "Fred Smith");
who->age = 21;
strcpy(who->color, "Red");
strcpy(who->education, "Some School");
}
void print_record(Person *who)
{
printf("name: %s\n", who->name);
printf("age: %d\n", who->age);
printf("color: %s\n", who->color);
printf("education: %s\n", who->education);
}
void load_db(FILE *fp, Person *who)
{
int result = fread(who, sizeof(*who), 1, fp);
if(!result)
printf("cannot load database");
}
FILE *connect_db(char *file_name, char *mode, Person *who)
{
FILE *fp = NULL;
if(strcmp(mode, "w") == 0)
{
fp = fopen(file_name, mode);
}
else if(strcmp(mode, "r") == 0)
{
fp = fopen(file_name, mode);
}
else
{
printf("incorrect mode");
}
return fp;
}
int save_db(FILE *fp, Person *who)
{
int result = fwrite(who, sizeof(*who), 1, fp);
if(result)
{
return 0;
}
printf("cannot save db");
return -1;
}
int main(int argc, char *argv[])
{
FILE* fp = NULL;
Person who1;
Person who2;
create_person(&who1);
print_record(&who1);
fp = connect_db("record2.dat", "w", &who1);
save_db(fp, &who1);
fclose(fp);
fp = connect_db("record2.dat", "r", &who2);
load_db(fp, &who2);
print_record(&who1);
fclose(fp);
return 0;
}

Resources