student programmer here. I am having trouble reading input from a binary file in C. The data stored in the file are structs that look like this:
typedef struct reserve{
char *name;
char *ic;
int room_number;
} Reserve;
I get no problem when I write the struct to a binary file. Here is the code I did for writing to the file:
void reg_new() //Make a new reservation
{
Reserve newReserve = {"", "", 0};
char name[NAMEFIELD], ic[ICFIELD];
int room_number;
FILE *resvptr;
scanf_s("%s", name, NAMEFIELD);
scanf_s("%s", ic, ICFIELD);
scanf_s("%d", &room_number);
newReserve.name = name;
newReserve.ic = ic;
newReserve.room_number=room_number;
resvptr = fopen("reservations.dat", "wb");
fwrite(&newReserve, sizeof(Reserve), 1, resvptr);
fclose(resvptr);
}
And the code for reading from the file:
void reg_view() //view a reservation.
{
FILE *seekptr;
Reserve viewReserve = {"", "", 0};
int read;
if ( (seekptr = fopen("reservations.dat", "rb")) == NULL)
{
puts("Error: file could not be found.");
}
else
{
while ( !feof(seekptr))
{
read = fread(&viewReserve, sizeof(Reserve), 1, seekptr);
if(read != 0)
{
printf("Name: %s\nIC number: %s\nRoom Number: %d\n", viewReserve.name, viewReserve.ic, viewReserve.room_number);
}
}
fclose(seekptr);
}
Immediately after writing to the binary file and accessing the function for reading, the room number (of int value) reads fine, but the two strings (name and IC number) read out as garbage characters. Subsequently, closing and restarting the program, then attempting to read from the file will give me an access violation and bad pointer error.
The trouble is with reading the strings from the file. I'm suspecting a memory leak but I can't say for sure...can anyone help? Thanks.
You are writing pointers to the file, not the actual data. When you read the pointer from the file, it's just a memory address that you probably don't have access to, even if you do it in the same process that wrote the file (because it was actually a pointer to a function-scoped stack variable).
If you want to store the records in that way, make your function store the actual string data rather than the pointer:
typedef struct reserve{
char name[NAMEFIELD];
char ic[ICFIELD];
int room_number;
} Reserve;
And just do this:
scanf_s("%s", newReserve.name, NAMEFIELD);
scanf_s("%s", newReserve.ic, ICFIELD);
scanf_s("%d", &newReserve.room_number);
The structure consists of the integer and the addresses of two arrays (pointers). When you write it out and read it back, you are reading and writing the addresses of the character arrays - not their contents. You need to modify your routine to write and read the contents (including lengths) and also to allocate the storage to hold the contents when you read them back in.
Related
I got some weird characters into the file... $0# ϊ ?0#
what did i do wrong in writing the struct?
Code:
int main (){
struct books {
char name[30];
int npages;
char author[30];
} book1;
book1.name = "1000 leagues under the sea";
book1.npages = 250;
book1.author = "Jules Verne";
FILE *book;
book = fopen("book.txt", "wb");
/* trying to write the struct books into a file called book.txt */
fwrite( &book1, sizeof(book1), 1, book);
fclose(book);
return 0;
}
i changed some things now i get a file written. but i dont' get the npages right in the file.... it's like "Jules Verne 0# Πώ" ϊ 1000 leagues under the sea ”" "
You are storing binary representation of your struct data in the file. The strange characters that you see in the file is exactly that: the binary representation of the npages field. Yes, it will look like a set of strange characters, just like it is supposed to.
If you want to see the number of pages stored as a human-readable (text) representation of the number, you have to convert it from binary to text representation manually or use I/O functions that will do that for you.
In fact, if you want to see everything represented in human-readable format, you need a text file, not a binary file. I.e. you need to open it as a text file and use formatted-output functions to write the data.
FILE *book = fopen("book.txt", "wt");
fprintf(book, "%s %d %s\n", book1.name, book1.npages, book1.author);
fclose(book);
sizeof(struct books)
The number of bytes being copied is struct books and you never care about the number of bytes needed to store the strings. sizeof(struct books) will just have sizeof(pointers) included and not the number of bytes held by the pointer.
You can have a char array like
char name[20]; /* some size */
char author[40];
Now sizeof(struct books) includes the sizeof(name) + sizeof(author)
So what my program should do is: Read a .txt file with this piece of code.
FILE *fp;
char filename[40],part1[4],part2[4];
int c=0,pt1,pt2;
printf("\nEnter a file name to open: ");
gets(filename);
if ((fp = fopen(filename, "r"))!= NULL)
{
printf("\nThe file %s was opened successfully!",filename);
}
else
{
printf("\nThe file didnt open succesfully!");
}
And then store each line in the row string like this.
fgets(part1,4,fp);
pt1 = atoi(part1);
struct input
{
char name[20],row[30],code[3],nPieces[3],needed[3],usage[3],nUses[3];
};
struct input list[pt1];
while (c++ < pt1 )
{
fgets(list[c].row,30,fp);
printf ("\n%s", list[c].row);
}
But the problem is that after that i must take the row string and cut it into pieces (for exp the 1st line of txt was <1 Glass 2 0 9 3 1> where each number represents something) So what i want is to put the "1" into the code[3] string the "Glass" into the name[30] string etc. I tried to make it work using the isspace() scaning the row string and whenever it found a space it would copy the row array from 0-(the space - 1) using strncpy(). For some reason when ever i run tha program is stops working. Anyone that could suggest anything?
It seems you want to allocate an array of size pt1, but that won't work because this is compile time and the value of pt1 is not known.
With:
struct input
{
char name[20],row[30],code[3],nPieces[3],needed[3],usage[3],nUses[3];
};
you declare a variable, but it seems you want to define a type, so:
typedef struct input
{
char name[20],row[30],code[3],nPieces[3],needed[3],usage[3],nUses[3];
};
and then later you must malloc the memory:
struct input list= calloc(pt1, sizeof(struct input));
The statement
struct input list[pt1];
should give a compiler error (does with my compiler).
So what i want is to put the "1" into the code[3] string the "Glass"
into the name[30] string etc.
It's much easier with sscanf() rather than isspace() and strncpy():
sscanf(list[c].row, "%2s%19s%2s%2s%2s%2s",
list[c].code,
list[c].name, list[c].nPieces, list[c].needed, list[c].usage,
list[c].nUses)
This question already has answers here:
Writing and reading (fwrite - fread) structures with pointers
(3 answers)
Closed 8 years ago.
I tried to write and read from a file with pointers in structures. But when I read from file I see some garbage value. I am using GCC 4.7.2 on Linux. Need some help.
Read:
//read from a file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[14];
FILE * fp = NULL;
fp = fopen("/media/Study/PhoneDirectory.dat","rb");
if(fp == NULL)
printf("Error opening file!!!");
fseek(fp,0,SEEK_SET);
if(fread(&phoneList[1],sizeof(LISTING),1,fp)==1)
printf("%s %s",phoneList[1].name,phoneList[1].phone);
fclose(fp);
return 0;
}
And write:
//Write to file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[2];
FILE * fp = NULL;
fp = fopen("/media/Study/PhoneDirectory.dat","wb");
phoneList[1].name = "Santosh";
phoneList[1].phone = "9657681798";
if(fwrite(&phoneList[1],sizeof(LISTING),1,fp)==1)
printf("inserted");
fclose(fp);
return 0;
}
Pointers are only meaningful in the application process that they originate from. If you write them to a file, as you're doing here, the values you read back will be meaningless — they will most likely point to uninitialized memory, or to memory which is being used for something else entirely.
You will need to come up with another way of writing this data to a file.
The problem you have is equivocating between char* and char[]. You can certainly assign a string literal to a char*, but you need to understand what the contents of a LISTING structure contain, and how you want to serialize and deserialize data to a file.
It does not make sense to save pointers from one process and read them into another process, so you probably want to save the contents (what a pointer points at). You want to store two values, (name, phone) to the file. Since you likely want to store the literal name and literal phone, let us consider what the file might look like:
roast duck|212-333-4444
peking duck|411-511-61111
duck soup|314-222-3333
free duck|800-111-2222
...
You need functions to serialize and deserialize your data. Since your LISTING type is pointers, you will need to allocate appropriate space for those values, as you read them, and you need functions (methods) to read serialized data from a file and write serialized data to a file.
Reading (you will need to allocate enough space),
int
listing_read(FILE*fp, LISTING* listing)
{
char name_buffer[100];
char phone_buffer[100];
if(!fp) return(-1);
if(!listing) return(-2);
int res = fscanf(fp,"%s|%s\n",name_buffer,phone_buffer);
if( !res ) {
//handle error here
}
//careful here, you cannot free if you didn't malloc/strdup
if(listing->name) free(listing->name);
if(listing->phone) free(listing->phone);
listing->name = strdup(name_buffer);
listing->phone = strdup(phone_buffer);
return(0);
}
Writing (you will need to provide proper formatting),
int
listing_write(FILE*fp, LISTING* listing)
{
if(!fp) return(-1);
if(!listing) return(-2);
fprintf(fp,"%s|%s\n",listing->name,listing->phone);
return(0);
}
Here is how you need to modify your code,
//read from a file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[14];
FILE* fp = NULL;
if( !(fp = fopen("/media/Study/PhoneDirectory.dat","rb")) ) {
printf("Error opening file!!!");
exit(1);
}
fseek(fp,0,SEEK_SET);
if( listing_read(fp,&phoneList[0]) >= 0 ) {
printf("%s %s",phoneList[0].name,phoneList[0].phone);
}
fclose(fp);
return 0;
}
And here is how writing the file would change,
//Write to file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[14];
FILE* fp = NULL;
if( !(fp = fopen("/media/Study/PhoneDirectory.dat","wb")) ) {
printf("error, cannot write file\n");
exit(1);
}
phoneList[0].name = "Santosh";
phoneList[0].phone = "9657681798";
if( listing_write(fp,&phoneList[0])>=0) {
printf("inserted");
}
fclose(fp);
return 0;
}
Note that in you writing program you assign the string literals "Santosh" and "9657681798" to the LISTING members name and phone. Though legal to do, you need a better understanding of what C does here. C takes the address of these C-string constants and assigns those addresses to the phonelist[1].name and phonelist[1].phone member pointers.
Consider that if you did this assignment,
phoneList[0].name = "Santosh";
phoneList[0].phone = "9657681798";
You have assigned the pointers to constant strings to your structure members.
But if you were to allocate space (for example, using strdup()),
phoneList[0].name = strdup("Santosh");
phoneList[0].phone = strdup("9657681798");
You have allocated space for the strings, assigning independent locations for these member elements. Which is is more likely what you want to do.
Note that I used phonelist[0] since C has zero-based arrays.
printf("%s %s",phoneList[1].name,phoneList[1].phone);
The above statement invokes undefined behaviour.
Since the pointers name & phone of struct object phoneList[1] are not initialized dereferencing them invokes UB. In your case they are throwing out garbage values but it could have lead to a crash also.
To fit your case of reading the contents of file and storing it in the struct objects use getline function to read them row-wise(assuming that all the details are stored line-wise) and then dynamically allocate the memory for char pointers then assign them to the read value. But, this approach leads to lot of memory management which is error prone.
I need to build an array of pointers to dynamically allocated structures (DBrecord) and fill that array with input from another file. Not sure how to approach this.
The data file will have the number of entries first, followed by entries in a specific order.
numOfEntries
lastName firstName studentID year gpa expGradYear
example:
1
Doe John 12345678 senior 3.14159 2015
Here's the code I have so far:
class.h
typedef enum {firstYear, sophomore, junior, senior, grad} class;
main.c
#include <stdio.h>
#include <stdlib.h>
#include "class.h"
int main(){
//DBrecord is name for structure
struct DBrecord{
int DBrecordID; //ID for each entry, range 0-319
char *last; //student last name
char *first; //student first name
char studentID[8]; //student ID
int age; //student age
class year; //year in school
float gpa; //GPA
int expGradYear; //expected graduation year
};
int numEntries; //total number of entries, first num in data file
struct DBrecord **ptrToDB;
//scan first int in data file and assign to numEntries
scanf("%d", &numEntries);
//allocate memory for structures, each is 36 bytes
*ptrToDB = malloc (sizeof(struct DBrecord) * numEntries);
//free allocated memory
free(ptrToDB);
//build an array of pointers to dynamically allocated structures
//fill that array with input from data file
//build 7 arrays of pointers to DBrecords, one for each field except DB ID
//sort each a different way
//note the 7 arrays are pointers, no copying
//print each of the 7 sorted arrays
return 0;
}
I can give you some snippets on how to look at this problem.
First - I would avoid using class name for any variable, because in many object-oriented programming languages (including C++) it is a keyword and can't be a name of variable.
Structure DBrecord
It might be a good idea to use typedef. Then you could declare a struct variable without using "struct DBrecord", just "DBrecord". But that's optional. This is how it would look:
typedef struct {
int DBrecordID; // ID for each entry
char *lastName;
char *firstName;
char studentID[8];
...
} DBrecord;
Loading from file
In this homework you have the number of records at the beginning of the file, so you don't need to take "extra" care about it. Just load it.
Let's assume the file is like this:
2
Doe John 12345678 senior 3.14159 2015
Carl Boss 32315484 junior 2.71 2013
Therefore the first thing you do with your file is to open it.
Portable way of working with files is by using FILE pointer. Let me show it (stdio.h must be included):
FILE *filePtr; // Define pointer to file
if((filePtr = fopen("records.txt", "r")) == NULL) // If couldn't open the file
{
printf("Error: Couldn't open records.txt file.\n"); // Printf error message
exit(1); // Exit the program
}
Then you can read from your file by line using fgets() to read by lines or fgetc() to read by characters. This is how you can read number of records (remember that it's on the first line and we've just opened the file - we are at the beginning of the file):
char buffer[100]; // Define the buffer
fgets(buffer, 100 /* size of buffer */, filePtr);
Now buffer contains the first line (without \n character) - number of records. Continue with converting the num's characters into integer (here stdlib.h also has to be included):
int numOfRecords = atoi(buffer);
Allocating enough DBrecords
Now you know the number of records, you can allocate enough space for them. We will use array of pointers.
DBrecord **recs;
recs = (DBrecord **) malloc(sizeof(DBrecord *) * numOfRecords);
Now we have created array of pointers, so now we need to allocate every individual pointer as a DBrecord. Using cycle:
int i;
for(i = 0; i < numOfRecords; i++)
{
recs[i] = (DBRecord *) malloc(sizeof(DBrecord));
}
Now you can acces array elements (= individual records) like this:
recs[0]->lastname /* two possibilities */
*(recs[0]).lastname
an so on.
Filling array with values from file
Now you know everything to get the homework done. This way you fill the array:
int i;
for(i = 0; i < numOfRecords; i++)
{
// Content of cycle reads one line of a file and parses the values into recs[i]->type...
/* I give you small advice - you can use fgetc(filePtr); to obtain character by character from the file. As a 'deliminer' character you use space, when you hit newline, then the for cycle continues.
You know the order of stored values in the file, so it shouldn't be hard for you.
If you don't get it, let me now in comments */
}
Is it somehow clearer now?
EDIT: File name as main's argument
There are usually two ways of 'passing' arguments (values) to a program. They are:
./program < records.txt // Here the file's name is passed to program on stdin
./program records.txt // Here the file's name is passed as value in `argv`.
If you can choose, I strongly recommend you the second one. Therefore you need to have main defined as this:
int main(int argc, char *argv[]) // this is important!
{
// code
return 0;
}
argc is integer which says, how much arguments were passed to the program. argv is array storing them. Remember, that the first argument is name of the program. Therefore if you need to check for it, do it:
if(argc != 2)
{
printf("Number of arguments is invalid\n");
exit(1); // exit program
}
Then you only put argv[1] into fopen function, instead of the string "records.txt".
EDIT 2: Reading file's name from stdin
Another approach must be done, if the name of the records file is passed to the program via ./program < records.txt, which means that "records.txt" (without quotes) will be passed (redirected) to program's standard input.
Therefore to handle that, you can do this:
char filename[50]; // buffer for file's name
scanf("%s", &filename); // reads standard input into 'filename' string until white character appears (new line, blank, tabulator).
Then you have your desired file's name in filename string.
Where to start, where to start.....
//allocate memory for structures, each is 36 bytes
mem = (double *)malloc(36*numEntries);
malloc should be malloc (sizeof (struct DBRecord) * numEntries);
don't cast the result of malloc
2a. you forgot stdlib.h
Why include class.h?
your array of pointers are not double, they are instead
struct DBRecord **ptrToDB;
*ptrToDB = malloc (sizeof (struct DBRecord) * numEntries);
This should get you started.
Next, free() should be the last thing you do before leaving your function (and yes, main is a function)
You'll have to insert some code for the next part, I can't do the homework for you.
I have a structure with the following definition:
typedef struct myStruct{
int a;
char* c;
int f;
} OBJECT;
I am able to populate this object and write it to a file. However I am not able to read the char* c value in it...while trying to read it, it gives me a segmentation fault error. Is there anything wrong with my code:
//writensave.c
#include "mystruct.h"
#include <stdio.h>
#include <string.h>
#define p(x) printf(x)
int main()
{
p("Creating file to write...\n");
FILE* file = fopen("struct.dat", "w");
if(file == NULL)
{
printf("Error opening file\n");
return -1;
}
p("creating structure\n");
OBJECT* myObj = (OBJECT*)malloc(sizeof(OBJECT));
myObj->a = 20;
myObj->f = 45;
myObj->c = (char*)calloc(30, sizeof(char));
strcpy(myObj->c,
"This is a test");
p("Writing object to file...\n");
fwrite(myObj, sizeof(OBJECT), 1, file);
p("Close file\n");
fclose(file);
p("End of program\n");
return 0;
}
Here is how I am trying to read it:
//readnprint.c
#include "mystruct.h"
#include <stdio.h>
#define p(x) printf(x)
int main()
{
FILE* file = fopen("struct.dat", "r");
char* buffer;
buffer = (char*) malloc(sizeof(OBJECT));
if(file == NULL)
{
p("Error opening file");
return -1;
}
fread((void *)buffer, sizeof(OBJECT), 1, file);
OBJECT* obj = (OBJECT*)buffer;
printf("obj->a = %d\nobj->f = %d \nobj->c = %s",
obj->a,
obj->f,
obj->c);
fclose(file);
return 0;
}
When you write your object, you're writing the pointer value to the file instead of the pointed-to information.
What you need to do is not just fwrite/fread your whole structure, but rather do it a field at a time. fwrite the a and the f as you're doing with the object, but then you need to do something special with the string. Try fwrite/fread of the length (not represented in your data structure, that's fine) and then fwrite/fread the character buffer. On read you'll need to allocate that, of course.
Your first code sample seems to assume that the strings are going to be no larger than 30 characters. If this is the case, then the easiest fix is probably to re-define your structure like this:
typedef struct myStruct{
int a;
char c[30];
int f;
} OBJECT;
Otherwise, you're just storing a pointer to dynamically-allocated memory that will be destroyed when your program exits (so when you retrieve this pointer later, the address is worthless and most likely illegal to access).
You're saving a pointer to a char, not the string itself. When you try to reload the file you're running in a new process with a different address space and that pointer is no longer valid. You need to save the string by value instead.
I would like to add a note about a potential portability issue, which may or may not exist depending upon the planned use of the data file.
If the data file is to be shared between computers of different endian-ness, you will need to configure file-to-host and host-to-file converters for non-char types (int, short, long, long long, ...). Furthermore, it could be prudent to use the types from stdint.h (int16_t, int32_t, ...) instead to guarantee the size you want.
However, if the data file will not be moving around anywhere, then ignore these two points.
The char * field of your structure is known as a variable length field. When you write this field, you will need a method for determining the length of the text. Two popular methods are:
1. Writing Size First
2. Writing terminal character
Writing Size First
In this method, the size of the text data is written first, followed immediately by the data.
Advantages: Text can load quicker by block reads.
Disadvantages: Two reads required, extra space required for the length data.
Example code fragment:
struct My_Struct
{
char * text_field;
};
void Write_Text_Field(struct My_Struct * p_struct, FILE * output)
{
size_t text_length = strlen(p_struct->text_field);
fprintf(output, "%d\n", text_length);
fprintf(output, "%s", p_struct->text_field);
return;
}
void Read_Text_Field(struct My_STruct * p_struct, FILE * input)
{
size_t text_length = 0;
char * p_text = NULL;
fscanf(input, "%d", &text_length);
p_text = (char *) malloc(text_length + sizeof('\0'));
if (p_text)
{
fread(p_text, 1, text_length, input);
p_text[text_length] = '\0';
}
}
Writing terminal character
In this method the text data is written followed by a "terminal" character. Very similar to a C language string.
Advantages: Requires less space than Size First.
Disadvantages: Text must be read one byte at a time so terminal character is not missed.
Fixed size field
Instead of using a char* as a member, use a char [N], where N is the maximum size of the field.
Advantages: Fixed sized records can be read as blocks.
Makes random access in files easier.
Disadvantages: Waste of space if all the field space is not used.
Problems when the field size is too small.
When writing data structures to a file, you should consider using a database. There are small ones such as SQLite and bigger ones such as MySQL. Don't waste time writing and debugging permanent storage routines for your data when they have already been written and tested.