I want to know why the code does not print the output.
What is the difference between binary and normal file modes?
#include<stdio.h>
#include <stdlib.h>
typedef struct book_details
{
char book_title[50];
int book_no;
float book_price;
}book_details;
int main()
{
book_details b;
FILE *fp;
fp = fopen("book_list.txt","w+");
if (fp == NULL)
printf("File not found");
fflush(stdin);
printf("Enter Book Title: \n");
gets(b.book_title);
printf("Enter Book ID Number: \n");
scanf("%d",&b.book_no);
printf("Enter Book Price: \n");
scanf("%f",&b.book_price);
fprintf(fp,"Here are the book details");
fwrite(&b,sizeof(b),1,fp);
while (fread(&b,sizeof(b),1,fp) > 0)
printf("%s %d %f\n",b.book_title,b.book_no,b.book_price);
fclose(fp);
}
What are the mistakes?
This happens because here same File Pointer fp is used for reading and writing.Your output file is a binary file so only fread() and fwrite() can be used here. You cannot use fprintf(fp,"Here are the book details"); in this case.This also cause error in reading.In such cases there are TWO solutions.
Using rewind().
Using the function rewind() we could rewind the File Pointer fp back to initial state to read the file.
Try this code :-
#include<stdio.h>
#include <stdlib.h>
typedef struct book_details
{
char book_title[50];
int book_no;
float book_price;
}book_details;
int main()
{
book_details b;
FILE *fp;
fp = fopen("book_list.txt","r+");
if (fp == NULL)
printf("File not found");
printf("Enter Book Title: \n");
gets(b.book_title);
printf("Enter Book ID Number: \n");
scanf("%d",&b.book_no);
printf("Enter Book Price: \n");
scanf("%f",&b.book_price); // removed fprintf();
fwrite(&b,sizeof(b),1,fp);
fflush(stdin);
rewind(fp); // Using rewind();
while (fread(&b,sizeof(b),1,fp) > 0)
printf("%s %d %f\n",b.book_title,b.book_no,b.book_price);
fclose(fp);
}
Using separate read and write FILE pointers.
Try this code:-
#include<stdio.h>
#include <stdlib.h>
typedef struct book_details
{
char book_title[50];
int book_no;
float book_price;
}book_details;
int main()
{
book_details b;
FILE *fpwrite,* fpread; // separate File pointers.
fpwrite = fopen("book_list.txt","w");
if (fpwrite == NULL)
printf("File not found");
printf("Enter Book Title: \n");
gets(b.book_title);
printf("Enter Book ID Number: \n");
scanf("%d",&b.book_no);
printf("Enter Book Price: \n");
scanf("%f",&b.book_price); // removed fprintf();
fflush(stdin);
fwrite(&b,sizeof(b),1,fpwrite);
fclose(fpwrite);
fpread = fopen("book_list.txt","r");
while (fread(&b,sizeof(b),1,fpread) > 0)
printf("%s %d %f\n",b.book_title,b.book_no,b.book_price);
fclose(fpread);
}
Separate File pointers are considered better since it improves Readability of source-code.
You have a large number of errors in your code. Most can be overcome by simply changing the way you write-to and then read-from your data file. There is no need to open the file in "w+" mode. You (1) write data, and then (2) read data. So simply open the file originally in append mode "a" (or "ab" to explicitly specify a binary write -- not required, but it does make what your are doing clear). Then close and open the file again to read.
When you write your book info out. Do NOT write "Here are the book details" to the file (it will break your read of struct from the file unless you offset the file position indicator beyond the unneeded text before starting your read of data).
Then close your file, you are done writing (validating the return of fclose after a write to catch any stream error not reported as you validated each write). Now simply open your file again in "r" (read) mode and loop over each struct read and outputting the values to the screen.
Rather than going over each error (including the use of gets() which from this day forward you will never never never do again) and the fflush (stdin) which is not supported by all OS's except for seekable streams, I have included comments inline below explaining how to handle the user-input, the write and the subsequent read. Aside from the workings of the code, the most important point I can make is to validate each and every input and output your code makes. That will save you untold amounts of time when things go wrong...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXT 50 /* if you need a constant #define one (or more) */
#define FNAM "book_list.txt"
#define DETAIL "Here are the book details"
typedef struct book_details {
char book_title[MAXT];
int book_no;
double book_price; /* don't use floating-point variables for currency */
} book_details_t; /* people get upset when you lose $ from rounding. */
int main (int argc, char **argv) { /* use arguments to pass info to main */
char *filename = argc > 1 ? argv[1] : FNAM; /* read from file given as */
book_details_t b = { .book_title = "" }; /* argv[1] or FNAM if none */
size_t len = 0; /* string length to aid with trimming '\n' */
FILE *fp;
fp = fopen (filename, "ab"); /* no need for "w+", "a" (append) will
* let you write new values to file,
* then reopen in "r" to read values
*/
if (fp == NULL) {
perror ("fopen-filename"); /* if the file isn't open, don't just */
return 1; /* output a msg, handle the error. */
}
// fflush(stdin); /* only valid on seekable streams or some OSs */
printf ("Enter Book Title: ");
fgets (b.book_title, MAXT, stdin); /* use fgets - NEVER ever gets */
len = strlen (b.book_title); /* get length of title */
if (len && b.book_title[len-1] == '\n') /* check len & ends with '\n' */
b.book_title[--len] = 0; /* replace '\n' with '\0' (0) */
else if (len == MAXT - 1) { /* otherwise title too long */
fprintf (stderr, "error: title too long.\n"); /* handle error */
return 1;
}
printf ("Enter Book ID Number: ");
if (scanf ("%d", &b.book_no) != 1) { /* must validate scanf return */
fprintf (stderr, "error: invalid book_no.\n"); /* every time! */
return 1;
}
printf("Enter Book Price: "); /* same thing for every input */
if (scanf ("%lf", &b.book_price) != 1) {
fprintf (stderr, "error: invalid book_price.\n");
return 1;
}
printf ("\n%s\n\n", DETAIL); /* don't write to file - will break read */
if (fwrite (&b, sizeof(b), 1, fp) != 1) { /* validate every write */
perror ("fwrite-b");
return 1;
}
if (fclose (fp) == EOF) { /* validate 'close after write' */
perror ("fclose after write");
return 1;
}
if ((fp = fopen (filename, "r")) == NULL) { /* validate open for read */
perror ("open for read");
return 1;
}
while (fread (&b, sizeof(b), 1, fp) > 0)
printf ("%s %d %f\n", b.book_title, b.book_no, b.book_price);
fclose(fp);
return 0;
}
(note: while C99+ will automatically return 0; at the end of main(), it is a good idea to include it)
Example Use/Output
$ ./bin/struct_read_after_write dat/struct_books.dat
Enter Book Title: Huck Finn
Enter Book ID Number: 10157
Enter Book Price: 19.99
Here are the book details
Huck Finn 10157 19.990000
Add next book:
$ ./bin/struct_read_after_write dat/struct_books.dat
Enter Book Title: Tom Sawyer
Enter Book ID Number: 10156
Enter Book Price: 22.95
Here are the book details
Huck Finn 10157 19.990000
Tom Sawyer 10156 22.950000
Check Binary File
$ hexdump -Cv dat/struct_books.dat
00000000 48 75 63 6b 20 46 69 6e 6e 00 00 00 00 00 00 00 |Huck Finn.......|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 00 00 ad 27 00 00 3d 0a d7 a3 70 fd 33 40 |.....'..=...p.3#|
00000040 54 6f 6d 20 53 61 77 79 65 72 00 00 00 00 00 00 |Tom Sawyer......|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000070 00 00 00 00 ac 27 00 00 33 33 33 33 33 f3 36 40 |.....'..33333.6#|
00000080
All good, but notice the wasted space from writing MAXT (50) chars for every title? (if you had not initialized b = { .book_title = "" }; what do you think would be in the file?) Later on you will want to serialize the data and only write the bytes containing data to the file, and precede the title by the number of characters to read -- but that is for another day.
Tweak to printf Formatting
You may also want to tidy up your output formatting a bit by including the field width modifier and left justification for the title, providing a fixed-width for the ID, and limiting the precision of the price to 2-places, e.g.
while (fread (&b, sizeof(b), 1, fp) > 0)
printf ("%-50s %6d $%.2f\n",
b.book_title, b.book_no, b.book_price);
Which would then produce tidier output of:
$ ./bin/struct_read_after_write dat/struct_books.dat
Enter Book Title: Validating All I/O
Enter Book ID Number: 101
Enter Book Price: 99.50
Here are the book details
Huck Finn 10157 $19.99
Tom Sawyer 10156 $22.95
My Dog with Fleas 10150 $9.99
Lucky Cats Have None 10151 $12.49
Fun with C 100 $59.21
Validating All I/O 101 $99.50
Look things over and let me know if you have further questions.
Related
Following is my program. I try to read the file rgb_values.txt and transfer its data to out.txt. rgb_values.txt is very long with about 2.7 million lines, but when I run the program and check out.txt; it has only 2.5 million lines (2554994 lines to be exact) which means that in this line the program reads to EOF. However, this should not be the case. I don't know what went wrong
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include <stdio.h>
int count=0;
void getOneByte(FILE *plaintext,unsigned char *pt,int i){
char line[4]={0x0};
fgets(line,sizeof(line),plaintext);
int x1=0x0;
char str[4];
sprintf(str,"%s",line);
sscanf(str,"%02x ",&x1);
pt[i]=x1;
}
void get16Bytes(FILE *plaintext,unsigned char * pt){
int i=0;
char sh;
for (int i = 0; i <=15; i++)
{
getOneByte(plaintext,pt,i);
if((sh=fgetc(plaintext))!=EOF){
ungetc(sh,plaintext);
}
else{
ungetc(sh,plaintext);
break;
}
}
}
void write16Bytes(FILE *ciphertext,unsigned char *ct){
int i;
for(i = 0; i < 16; i++){
if(count == 0){
fprintf(ciphertext,"%02x",ct[i]);
count++;
}
else if(count == 1){
fprintf(ciphertext," %02x",ct[i]);
count++;
}
else if(count == 2){
fprintf(ciphertext," %02x\n",ct[i]);
count = 0;
}
}
}
int main(){
FILE *fp=fopen("rgb_values.txt","r");
FILE *fo=fopen("out.txt","w");
char ch;
unsigned char pt[16];
int i=15;
int cnt=0;
while(1){
memset(pt,'a',sizeof(pt));
get16Bytes(fp,pt);
if((ch=fgetc(fp))!=EOF){
ungetc(ch,fp);
}
else{
break;
}
write16Bytes(fo,pt);
cnt++;
}
fclose(fp);
return 1;
}
Link for rgb_values.txt
Link for my out.txt
Part of my input file:
c1 c1 c1
ff ff ff
ff ff ff
ff ff ff
ff ff ff
fe fe fe
fd fd fd
ff ff ff
fb fb fb
fe fe fe
fe fe fe
ff ff ff
fb fb fb
f1 f1 f1
e9 e9 e9
e6 e6 e6
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e5 e5 e5
e2 e2 e2
e5 e5 e5
e7 e7 e7
e5 e5 e5
e4 e4 e4
e3 e3 e3
e3 e3 e3
e8 e8 e8
de de de
9c 9c 9c
3b 3b 3b
01 01 01
02 02 02
00 00 00
02 02 02
0a 0a 0a
I don't know what went wrong
Coding is not defensive as it does not look for unexpected events.
To learn what went wrong, improve code's error detection.
In other words, don't trust user input - its evil.
Why read only 3 bytes?
The below reads, at most 3 bytes from the file, leaving the remainder of a line for later reading.
char line[4]={0x0};
fgets(line,sizeof(line),plaintext);
It does not read only 3 bytes from a file and toss the remainder of the line.
Check return value of fgets()
// fgets(line,sizeof(line),plaintext);
if (fgets(line,sizeof(line),plaintext) == NULL) {
TBD_alert();
}
Use int
int fgetc() can return 257 different values. Saving in a char loses something.
// char ch;
int ch;
// char sh;
int sh;
Lack of error checking
What if sscanf(str,"%02x ",&x1) returns 0 (no conversion)?
What if 4 is too small?
Should an extra white-space exist, (space, '\r', ...), 4 is simply too small.
char line[4]={0x0};
fgets(line,sizeof(line),plaintext);
Instead read a complete line with fgets() allowing at least a 2x size buffer over expected size and tolerate extra white-space or a missing '\n' on the last line. Given a line of "c1 c1 c1\n", use a buffer size of (3*3 + 1)*2.
I suspect many '\r' lurking about.
Avoid UB. Use matching specifiers
"%x" matches an unsigned, not int.
// int x1=0x0;
unsigned x1 = 0x0;
...
// '0' is useless, '2' is not needed. Trailing space is useless
// sscanf(str,"%02x ",&x1);
sscanf(str,"%x", &x1); // Check return value - not shown
or saved directly
sscanf(str, "%hhx", &pt[i]);
What is other than 3 values per line?
Sample code to read a line of 3 hex values and detect lots of errors.
#define EXPECTED_LINE_SIZE (3*3 + 1 /* for the \0 */)
char line[EXPECTED_LINE_SIZE * 2];
if (fgets(line, sizeof line, plaintext) == NULL) {
return failure;
}
int n = 0;
unsigned val[3];
sscanf(line "%2x %2x %2x %n", &val[0], &val[1], &val[2], &n);
if (n == 0 || line[n] != '\0') {
return failure;
}
Check if fopen() failed
FILE *fp=fopen("rgb_values.txt","r");
FILE *fo=fopen("out.txt","w");
if (fp == NULL || fo == NULL) {
TBD_Error_out(); // Add your code here
}
If you just want to copy from one file to the other, open in binary mode, and in a loop fread a buffer of say 4KiB, and then fwrite it to the other file. Quick and easy:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *in = fopen(..., "rb");
FILE *out = fopen(..., "wb");
if (!in || !out)
return EXIT_FAILURE;
char buffer[4096];
size_t nread;
while ((nread = fread(buffer, 1, sizeof buffer, in)) > 0)
fwrite(buffer, 1, nread, out);
}
Otherwise if you need to read it as strings use fgets and fputs in a loop:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *in = fopen(..., "r");
FILE *out = fopen(..., "w");
if (!in || !out)
return EXIT_FAILURE;
char line[256];
while (fgets(line, sizeof line, in) != NULL)
fputs(line, out);
}
Or as I mentioned, fgets to read a whole line, sscanf to parse out all three values, pass back to the calling function (together with a file read success status), and fprintf all three values at once:
#include <stdio.h>
#include <stdlib.h>
int read_values(FILE *in, int *values)
{
char line[256];
if (fgets(line, sizeof line, in) == NULL)
return 0; // Failure to read
if (sscanf(line("%x %x %x", &values[0], &values[1], &values[2]) != 3)
return 0; // Failure to parse
return 1; // Success
}
void write_values(FILE *out, int *values)
{
fprintf(out, "%02x %02x %02x\n", values[0], values[1], values[2]);
}
int main(void)
{
FILE *in = fopen(..., "r");
FILE *out = fopen(..., "w");
if (!in || !out)
return EXIT_FAILURE;
int values[3];
while (read_values(in, values))
write_values(out, values);
}
Note that I don't close the files. If your program is doing more after this, then they should really be closed. If the program just exits, then the system will close them for us.
When I open and read a text file it works. However, when I open and try to read a binary file it stops at the first byte equal to 00.
For example, I try to read a png file, with the below first line in hexdump:
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
But, I end up with a string that includes only these:
00000000 89 50 4e 47 0d 0a 1a 0a 0a |.PNG.....|
This is my code.
int main(int argc, char *argv[]) {
int num = 5;
char *filename;
char *myfile;
FILE *fp;
filename = argv[1];
if((fp = fopen(filename,"rb")) == NULL) {
printf("error open file\n");
exit(1);
}
myfile = (char *) malloc(num*sizeof(char));
if(myfile==NULL) {
printf("error memory\n");
exit(1);
}
int i=0;
while(!feof(fp)) {
fread(myfile+i,sizeof(char),1,fp);
if(i==num) {
myfile = (char *) realloc(myfile,sizeof(char)*(num*2));
if(myfile==NULL) {
printf("error memory\n");
exit(1);
}
num = num*2;
}
i++;
}
fclose(fp);
printf("%s\n",myfile);
return(0);
}
You're reading the entire file correctly. Instead, the problem lies here:
printf("%s\n",myfile);
printf doesn't know when your myfile string ends, and so assumes it's a cstring, which is terminated by the first null character.
You'd want to use fwrite instead:
fwrite(myfile, num, 1, stdout);
This isn't a complete working alternative, though -- from what I see in your code, num is likely to be higher unless your file's size is an exact power of two. You'd want to rewrite your program so that you know the exact size of the data you've read, and then replace num with that in fwrite.
I'm trying to run a program wherein, the user inputs a number of employees to read and the program displays the first n employees.
#include <stdio.h>
struct employee {
int id;
char name[50];
float salary;
}
struct employee e;
adddata(){
FILE *fp;
fp=fopen("record.txt","a");
printf(" Employee Name : ");
scanf("%s",e.name);
printf(" ID :");
scanf("%d",&e.id);
printf(" Salary :");
scanf("%f",e.salary);
fseek(fp,0,SEEK_END);
fwrite(&e,sizeof(e),1,fp);
fclose(fp);
}
get(){
int n;
printf("amount to display: ");
scanf("%d", &n);
FILE *fp;
fp = fopen("record.txt", "r");
//code to get and display first n employees
//e.g. n = 3; displays employee 1, 2, 3
}
int main(){
int opt;
printf("1 - add data \n 2 - get data");
scanf("%d", &opt);
switch(opt){
case 1 : adddata();
break;
case 2 : get();
}
}
content of the external text:
id name salary
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
How do you read it (not line by line but by struct variable)?
Given your addition showing you are writing the structures to your file in binary mode changes the character of your original question quite a bit. Rather than being one of read and parsing values from the text file as shown in your original question, it become one of simply reading a given number of structures, all of fixed size, from a binary file.
Since the original question indicated a read and parse of text, that will provide an opportunity for doing both while answering your question in total. So let's read and parse the original text you provided, then write a binary file containing the structures and then read from that file and display the output for the requested number of employees. (adding any header information you like to the output is left to you)
Commandment No. 6 For C Programmers - "ye be warned..."
First, your adddata() function is horribly fragile and a recipe for invoking Undefined Behavior on an innocent errant keystroke. You cannot use any input function properly (any function for that matter) without checking the return. Lest ye violate Commandment No. 6 of Henry Spencer's 10 Commandments for C Programmers.
Avoid Magic-Numbers & Hardcoded Filenames
Additionally, don't use Magic-Numbers or Hardcoded Filenames in your code. If you need a constant, #define one (or more) and pass the filename to read from as an argument to main() or take it as input. For example:
#define MAXC 512 /* max characters for read buffer */
#define MAXN 50 /* max characters in name */
struct employee {
int id;
char name[MAXN];
float salary;
};
and
int main (int argc, char **argv) {
int n, nemp; /* n for user input, nemp for number read from file */
char ofn[MAXC], /* buffer to hold output file name for writing bin file */
*p = ofn; /* pointer to output file name buffer to swap extensions */
FILE *fp; /* file pointer */
struct employee e[MAXN] = {{ .id = 0 }}; /* array of MAXN employee */
if (argc < 2 ) { /* validate 1 argument given for filename */
fprintf (stderr, "error: insufficient input,\n"
"usage: %s filename\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
Original Question Text File Read
On to handling your data. For your original data posted as text, were you faced with reading and parsing the data into your struct employee, you simply ignore all lines that do not begin with isdigit().
Then to extract the information, you would basically work from the ends to the middle, reading the id and saving the number of characters that took. You would then backup from the end of the line until your first whitespace character (using strrchr()) and then convert the salary to float. All that is left is scanning forward from the end ofiduntil the first non-whitespace character is found, and then backing up from the space beforesalaryuntil the first non-whitespace character is found. What is left between is thenamewhich you can copy withmemcpy()` (don't forget to *nul-terminate).
Putting that together in a readtext() function that takes an open FILE* stream pointer, a pointer to the array of struct employee to fill (which is presumed large enough to hold the data read from the file -- you should add an additional validation by passing the max size as another parameter), and finally the number of employees to read. You could do something similar to:
/** read text file as shown in question into array of struct */
int readtext (FILE *fp, struct employee *emp, int n)
{
int nemp = 0; /* number of employees read */
char buf[MAXC]; /* buffer to hold each line (don't skimp on size) */
struct employee tmp = { .id = 0 }; /* temp struct to fill */
while (nemp < n && fgets (buf, MAXC, fp)) { /* read each line */
char *p, *ep; /* pointer & end-pointer to use in parsing */
int offset; /* offset (no. of chars for id) */
if (!isdigit (*buf)) /* if 1st char not digit, get next line */
continue;
/* if no successful convertion to int for id, get next line */
if (sscanf (buf, "%d%n", &tmp.id, &offset) != 1)
continue;
/* if space before salary or no conversion to float, get next line */
if (!(ep = strrchr (buf, ' ')) || sscanf (ep, "%f", &tmp.salary) != 1)
continue;
p = buf + offset; /* skip whitespace until start of name */
while (isspace (*p))
p++;
do /* backup until last char in name found */
ep--;
while (ep > p && isspace(*ep));
ep++; /* advance 1 past last char in name */
memcpy (tmp.name, p, ep - p); /* copy name to tmp.name */
tmp.name[ep - p] = 0; /* nul-terminate tmp.name */
emp[nemp++] = tmp; /* assign tmp to array, increment nemp */
}
return nemp; /* return number of employees read into array */
}
And you would call it from main to fill your array of struct e as:
...
fputs ("amount to display: ", stdout); /* prompt */
fflush (stdout); /* optional (but recommended) */
if (scanf ("%d", &n) != 1) { /* validate integer input */
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
if ((nemp = readtext(fp, e, n)) != n) /* if less than n read, warn */
fprintf (stderr, "\nwarning: less than %d employees found.\n\n", n);
fclose (fp); /* close file */
...
You can check you have the information you need by simply outputting the data:
putchar ('\n'); /* add newline before output */
for (int i = 0; i < nemp; i++) /* output employees read from file */
printf ("%3d %-20s %.2f\n", e[i].id, e[i].name, e[i].salary);
The rest deals with writing data to your binary file that can then be read as your updated question indicates is the true problem. Both writing and then reading back from the binary file are simple calls to fwrite and fread. You can make functions for each, but that is left to you.
Writing Binary File To Use With Updated Question
Since we are writing the binary data from the information read from the text file, all that need to happen is to write the information out under a new filename. Simply swapping file extensions from ".txt" to ".bin" is a simple way to distinguish the files. If there is no extension on the input file, then simply add the ".bin" extension to the end. (You should also add additional validations that the filename will fit in the space provided by ofn -- that is also left to you)
For this example we simply find the last '.' and consider everything to the right to be the file extension. This can be expanded upon as needed. A simple implementation would be:
/* form output filename by changing extension to ".bin" */
if ((p = strrchr (argv[1], '.')) == NULL) /* if no last '.' in argv[1] */
strcpy (ofn, argv[1]); /* copy all to ofn */
else { /* otherwise */
memcpy (ofn, argv[1], p - argv[1]); /* copy up to last '.' to ofn */
ofn[p - argv[1]] = 0; /* nul-terminate */
}
strcat (ofn, ".bin"); /* concatenate .bin extension */
With the output filename now formed, simply write the structures out, and close the file stream (note: always validate close-after-write to catch any errors in flushing the file stream that would not have been caught by your validation of fwrite)
/* open/validate output file open for writing */
if (!(fp = fopen (ofn, "wb"))) {
perror ("fopen-idnamesal.bin");
return 1;
}
/* write array to output file in binary */
if (fwrite (e, sizeof *e, nemp, fp) != (size_t)nemp) {
fputs ("error: short write to binary.\n", stderr);
return 1;
}
if (fclose (fp) == EOF) { /* always validate close-after-write */
perror ("fclose-after-write");
return 1;
}
Reading struct employee From Binary File
Now we have a binary file containing your structures that we can work with to answer your updated question. While you can use your array-of-struct to read the data into, since you do not know how many struct employee you will read, this is traditionally where you would allocate a block of memory of sufficient size to hold the number of struct employee entered by the user using malloc(). This give you the ability to size your storage at run-time to the exact amount needed to hold your data. It is simple to do, but follow Commandment No. 6, e.g.
/* allocate block of storage to hold data read from binary file */
struct employee *empfrombin = malloc (nemp * sizeof *empfrombin);
if (!empfrombin) {
perror ("malloc-empfrombin");
return 1;
}
With a block of memory of sufficient size to hold the number of struct employee entered by the user, simply read that number of struct from your binary file into the new block with a single call to fread and close the file (no need to validate close-after-read). You can then output the information in the same manner as done above:
/* read/validate struct data from binary file into newly allocated blockm */
if (fread (empfrombin, sizeof *empfrombin, nemp, fp) != (size_t)nemp) {
fprintf (stderr, "error: file read failed - '%s'.\n", ofn);
return 1;
}
fclose (fp); /* close file */
/* output employees from newly allocated/filled block of memory */
fprintf (stdout, "\nemployees read from binary file '%s'\n\n", ofn);
for (int i = 0; i < nemp; i++)
printf ("%3d %-20s %.2f\n",
empfrombin[i].id, empfrombin[i].name, empfrombin[i].salary);
Lastly, in any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. So simply free() your block of memory and you are done:
free (empfrombin); /* don't forget to free what you have allocated */
}
Putting it altogether, the entire example covering your original and update question would be:
#include <stdio.h>
#include <stdlib.h> /* for malloc/free */
#include <string.h> /* for strrchr, memcpy */
#include <ctype.h> /* for isspace, isdigit */
#define MAXC 512 /* max characters for read buffer */
#define MAXN 50 /* max characters in name */
struct employee {
int id;
char name[MAXN];
float salary;
};
/** read text file as shown in question into array of struct */
int readtext (FILE *fp, struct employee *emp, int n)
{
int nemp = 0; /* number of employees read */
char buf[MAXC]; /* buffer to hold each line (don't skimp on size) */
struct employee tmp = { .id = 0 }; /* temp struct to fill */
while (nemp < n && fgets (buf, MAXC, fp)) { /* read each line */
char *p, *ep; /* pointer & end-pointer to use in parsing */
int offset; /* offset (no. of chars for id) */
if (!isdigit (*buf)) /* if 1st char not digit, get next line */
continue;
/* if no successful convertion to int for id, get next line */
if (sscanf (buf, "%d%n", &tmp.id, &offset) != 1)
continue;
/* if space before salary or no conversion to float, get next line */
if (!(ep = strrchr (buf, ' ')) || sscanf (ep, "%f", &tmp.salary) != 1)
continue;
p = buf + offset; /* skip whitespace until start of name */
while (isspace (*p))
p++;
do /* backup until last char in name found */
ep--;
while (ep > p && isspace(*ep));
ep++; /* advance 1 past last char in name */
memcpy (tmp.name, p, ep - p); /* copy name to tmp.name */
tmp.name[ep - p] = 0; /* nul-terminate tmp.name */
emp[nemp++] = tmp; /* assign tmp to array, increment nemp */
}
return nemp; /* return number of employees read into array */
}
int main (int argc, char **argv) {
int n, nemp; /* n for user input, nemp for number read from file */
char ofn[MAXC], /* buffer to hold output file name for writing bin file */
*p = ofn; /* pointer to output file name buffer to swap extensions */
FILE *fp; /* file pointer */
struct employee e[MAXN] = {{ .id = 0 }}; /* array of MAXN employee */
if (argc < 2 ) { /* validate 1 argument given for filename */
fprintf (stderr, "error: insufficient input,\n"
"usage: %s filename\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
fputs ("amount to display: ", stdout); /* prompt */
fflush (stdout); /* optional (but recommended) */
if (scanf ("%d", &n) != 1) { /* validate integer input */
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
if ((nemp = readtext(fp, e, n)) != n) /* if less than n read, warn */
fprintf (stderr, "\nwarning: less than %d employees found.\n\n", n);
fclose (fp); /* close file */
putchar ('\n'); /* add newline before output */
for (int i = 0; i < nemp; i++) /* output employees read from file */
printf ("%3d %-20s %.2f\n", e[i].id, e[i].name, e[i].salary);
/* form output filename by changing extension to ".bin" */
if ((p = strrchr (argv[1], '.')) == NULL) /* if no last '.' in argv[1] */
strcpy (ofn, argv[1]); /* copy all to ofn */
else { /* otherwise */
memcpy (ofn, argv[1], p - argv[1]); /* copy up to last '.' to ofn */
ofn[p - argv[1]] = 0; /* nul-terminate */
}
strcat (ofn, ".bin"); /* concatenate .bin extension */
/* open/validate output file open for writing */
if (!(fp = fopen (ofn, "wb"))) {
perror ("fopen-idnamesal.bin");
return 1;
}
/* write array to output file in binary */
if (fwrite (e, sizeof *e, nemp, fp) != (size_t)nemp) {
fputs ("error: short write to binary.\n", stderr);
return 1;
}
if (fclose (fp) == EOF) { /* always validate close-after-write */
perror ("fclose-after-write");
return 1;
}
/* allocate block of storage to hold data read from binary file */
struct employee *empfrombin = malloc (nemp * sizeof *empfrombin);
if (!empfrombin) {
perror ("malloc-empfrombin");
return 1;
}
if (!(fp = fopen (ofn, "rb"))) { /* open/validate binary file */
perror ("fopen-ofn-rb");
return 1;
}
/* read/validate struct data from binary file into newly allocated blockm */
if (fread (empfrombin, sizeof *empfrombin, nemp, fp) != (size_t)nemp) {
fprintf (stderr, "error: file read failed - '%s'.\n", ofn);
return 1;
}
fclose (fp); /* close file */
/* output employees from newly allocated/filled block of memory */
fprintf (stdout, "\nemployees read from binary file '%s'\n\n", ofn);
for (int i = 0; i < nemp; i++)
printf ("%3d %-20s %.2f\n",
empfrombin[i].id, empfrombin[i].name, empfrombin[i].salary);
free (empfrombin); /* don't forget to free what you have allocated */
}
(note: try and add the additional validations mentioned in the paragraphs above to ensure you can read no more data from the text file than you have available array storage for)
Original Text Input File Used
$ cat dat/idnamesal.txt
id name salary
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
Resulting Binary File Written
$ hexdump -C dat/idnamesal.bin
00000000 01 00 00 00 4a 6f 68 6e 20 44 6f 65 00 00 00 00 |....John Doe....|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000030 00 00 00 00 00 00 00 00 00 40 1c 46 02 00 00 00 |.........#.F....|
00000040 4b 79 6c 65 20 53 6d 69 74 68 00 00 00 00 00 00 |Kyle Smith......|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000070 00 00 00 00 00 60 6a 46 03 00 00 00 52 6f 6e 20 |.....`jF....Ron |
00000080 41 64 61 6d 73 00 00 00 00 00 00 00 00 00 00 00 |Adams...........|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000b0 00 40 9c 46 04 00 00 00 41 6c 69 63 65 20 57 69 |.#.F....Alice Wi|
000000c0 6c 64 65 00 00 00 00 00 00 00 00 00 00 00 00 00 |lde.............|
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 10 a4 46 |...............F|
000000f0 05 00 00 00 5a 6f 65 20 4a 6f 72 64 61 6e 00 00 |....Zoe Jordan..|
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000120 00 00 00 00 00 00 00 00 00 a0 8c 46 |...........F|
0000012c
Example Use/Output
Read from text format shown, write binary file of struct employee and read from binary file and display all 5 employees.
$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 5
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
employees read from binary file 'dat/idnamesal.bin'
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
Read less than all:
$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 1
1 John Doe 10000.00
employees read from binary file 'dat/idnamesal.bin'
1 John Doe 10000.00
Attempt to read more employees than exist in file:
$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 100
warning: less than 100 employees found.
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
employees read from binary file 'dat/idnamesal.bin'
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
Look things over and let me know if you have further questions. While your question changed a bit, both the text operations from the original and then binary operations from the update question are "bread-and-butter" operations you will carry out time and again, so you may as well make friends with both.
int get()
{
int n,fd;
struct employee emp;
printf("amount to display: ");
scanf("%d", &n);
fd = open("record.txt", 0666);
while(n--) {
if(read(fd,&emp,sizeof(emp)) != sizeof(emp)) {
return -1;
}
print_employee(&emp);
}
return 0;
}
Iam having a problem with my school project. When i try to read in C program some hex data from a binary file i get some data filled with F's like this
00 64 61 56 03 00 00 00 09 00 00 00 73 00 00 00
6A 69 75 FFFFFFE8 FFFFFFD1 5B FFFFFF8C FFFFFF93 73 FFFFFFAF 19 FFFFFF87 FFFFFFC1 4D FFFFFFFD FFFFFF93
FFFFFFDF FFFFFFBE FFFFFFA0 09 FFFFFFBE FFFFFFD4 FFFFFFEB FFFFFFDE FFFFFFD3 FFFFFFF9 FFFFFFBD FFFFFFE6 FFFFFFFA FFFFFFAA 7E 29
FFFFFFD3 FFFFFFE2 13 FFFFFFA5 3F FFFFFFA0 3A FFFFFFB2 50 53 3B 12 FFFFFFA1 39 FFFFFFA6 FFFFFF82
FFFFFFF7
While the original file in hexdump looks like this:
0000000 6400 5661 0003 0000 0009 0000 0073 0000
0000010 696a e875 5bd1 938c af73 8719 4dc1 93fd
0000020 bedf 09a0 d4be deeb f9d3 e6bd aafa 297e
0000030 e2d3 a513 a03f b23a 5350 123b 39a1 82a6
0000040 00f7
Iam not quiet sure, why is this happening.
I tested it using this code:
int main()
{
char ch, file_name[25];
FILE *fp;
printf("Enter the name of file you wish to see\n");
fgets(file_name, sizeof(file_name), stdin);
file_name[strlen(file_name)-1] = 0x00;
fp = fopen(file_name,"rb"); // read binary mode
if( fp == NULL ) //error checking
{
perror("Error while opening the file.\n");
exit(EXIT_FAILURE);
}
printf("The contents of %s file are :\n", file_name);
int i;
while( ( ch = fgetc(fp) ) != EOF )
{
printf("%02X ",ch);
if( !(++i % 16) ) putc('\n', stdout);
}
fclose(fp);
putc('\n', stdout);
return 0;
}
You just tripped on the sign extension done by the processor when expanding a signed integer value to a larger integer type:
The type char is only one byte wide, while the printf() function expects int arguments (four bytes on your system). Since char is signed on your system, the other 24 bits are filled with copies of the sign bit, producing the FFFFFF pattern that you see in your output. Note that your output is correct, except for the FFFFFF-prefixes to all bytes that start with a hex digit greater or equal to 8.
You can avoid this by simply declaring ch as unsigned char. That will be zero extended to an int, and your output will be correct.
If you make ch an unsigned character you won't see the sign extension (the FFs).
It may not be a good idea to open a file as a binary file, and trying reading it by fgetc(), which deals with text files. Instead, you can use fread() to read binary files.
Here is my solution:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int main()
{
char file_name[25];
puts("Enter the name of file you wish to see");
fgets(file_name, sizeof (file_name), stdin);
file_name[strlen(file_name) - 1] = '\0';
FILE *fp;
fp = fopen(file_name, "rb"); // read binary mode
if(!fp) //error checking
{
perror("fopen()");
exit(EXIT_FAILURE);
}
fseek (fp, 0, SEEK_END); // non-portable
long fSize = ftell(fp);
rewind(fp);
printf("The contents of %s file are :\n", file_name);
uint8_t *ch = malloc(fSize); // assuming CHAR_BIT == 8
fread(ch, 1, fSize, fp);
int i;
for(i = 0; i < fSize; i++)
{
printf("%.2x ", ch[i]);
if(!((i + 1) % 16))
putchar('\n');
}
fclose(fp);
putchar('\n');
return EXIT_SUCCESS;
}
This code has been tested on my machine, and its output is identical to that of hex dump. Feel free to ask me about the details.
make ch an unsigned char as it is treating the binary value as signed and the MSB as sign bit.
I have a file, such as the following (in hex).
1F 00 48 3A 18 00 00 00 53 00 70 00 6F 00 75 00
73 00 65 00 5F 00 61 00 7A 00 61 00 6D 00 00 00
I am trying to read the binary data from the file and output it as text. My current code is as follows:
#include<stdlib.h>
#include<stdio.h>
#include<iostream.h>
int main()
{
FILE *pFile, *tempFile;
pFile = fopen("C:\\wab files\\Admin.wab", "rb");
if(pFile == NULL)
{
fputs("file error", stderr);
exit(1);
}
tempFile = fopen("C:\\myfile.text","wb");
if(tempFile == NULL)
{
fputs("file not open", stderr);
exit(2);
}
int Contact_Id, Id_Size, Data_Info=0;
fread(&Contact_Id, 1, 4, pFile);
fread(&Id_Size, 1, 4, pFile);
Data_Info = Id_Size;
char* Main_buffer = (char*)malloc(Data_Info*sizeof(Data_Info));
fread(Main_buffer, 1, Id_Size, pFile);
const wchar_t* src = (unsigned short *) Main_buffer;
wcstombs ((char*) Main_buffer, src, Data_Info );
fwrite(Main_buffer, 1, Data_Info, tempFile);
free(Main_buffer);
return 0;
}
The output text file containts follow:
Spouse_azam _ a z a m
Why is _ a z a m shown in text file? I wants to write only Spouse_azam.
First of all this is wrong:
char* Main_buffer = (char*)malloc(Data_Info*sizeof(Data_Info));
It should be
char* Main_buffer = (char*)malloc(Data_Info*sizeof(char));
Second, you are using the same buffer (Main_buffer) for both source and destination. The result is that the first part of the buffer is overwritten with the converted string, but the rest of the buffer is left untouched. You have 24 bytes initially in the buffer (if you allocate correctly), and the first 11 are overwritten, but the other 13 are left there.
There are two options: use a second buffer for the multi-byte string, or use the return value of wcstombs to know how many bytes the result string has:
int bytes = wcstombs ((char*) Main_buffer, src, Data_Info );
fwrite(Main_buffer, 1, bytes, tempFile);