Files in C, accessing pointers, reading and writing in files - c

I'm trying to make a program in C which tracks the books borrowed by a student. I am having a hard time in accessing pointers with files. When I use files, I don't usually you fscanf( ), instead I use the usual scanf. I have this data struct:
typedef struct{
char fName[24], mInitial, lName[16];
}nameType;
typedef struct{
unsigned long idNo;
nameType studName;
char course[8];
int yrLevel;
books borrowedBooks;
int bksCtr;
}student;
typedef struct{
student *studs;
int studCtr;
}studList;
I have created two functions as of now, which is addStudToFile(void), which adds students to the file, and displayStudsFromFile(void), which basically prints out the students that were added in the file. These are my newbie function code:
void addStudToFile(void)
{
FILE *fp;
studList myStud;
fp = fopen("students.db", "w");
if(fp!=NULL){
/* ask for student details and adds these to the file */
printf("Enter ID number: ");
fflush(stdin);
scanf(,"%lu", &myStud.studs->idNo);
printf("Enter First Name: ");
fflush(stdin);
gets(myStud.studs->studName.fName);
printf("Enter Last Name: ");
fflush(stdin);
gets(myStud.studs->studName.lName);
printf("Enter Middle Initial: ");
fflush(stdin);
scanf("%c", &(myStud.studs->studName.mInitial));
printf("Enter Course: ");
fflush(stdin);
gets(myStud.studs->course);
printf("Enter Year: ");
fflush(stdin);
scanf("%d", &(myStud.studs->yrLevel));
fwrite(&myStud, sizeof(studList),1,fp);
fclose(fp);
}
}
and
void displayStudsFromFile(void)
{
FILE *fp;
studList myStud;
fp = fopen("students.db", "r");
if(fp!=NULL){
while (fread(&myStud, sizeof(studList), 1, fp)){
printf("%lu\t %s, %s %s\t %s-%d", myStud.studs->idNo, myStud.studs->studName.lName,
myStud.studs->studName.fName, myStud.studs->studName.mInitial,
myStud.studs->course, myStud.studs->yrLevel);
printf("borrowed %d books", myStud.studs->bksCtr);
}
fclose(fp);
}
}
Now, my problem here is in accessing my List which is myStud. In my addStudToFile( ) function, everytime I input my ID number, my program stops working. Why does it stops working? Do I have to malloc something? Or is my accessing in scanf() wrong? Another situation where I encounter my program to stop working again, is when I call my display function. It displays something, but alien/garbage values.
Here is a screenshot on where I encountered my problem in my scanning function:
And here is on my display function:
I hope someone can help me with this. Thanks!

Your hunch is right, you do need to malloc something :)
typedef struct{
student *studs;
int studCtr;
}studList;
Here's your problem. You're defining studs as a pointer to a student struct, but you are not actually allocating any memory for it, so you can later reference it with the -> operator.
You can either allow for a preset number of entries, so you could then define studs like,
student studs[10];
to allow for 10 entries, or in addStudToFile() you could ask the user to input how many entries he would like to give. In this case you would leave the definition as it is and as soon as you have the user input do:
myStud.studs = (student *) malloc( sizeof(student) * how_many );
There may be more bugs along the code you have posted, but for the time being the above is what keeps you back.
edit: if you follow the malloc() route, before returning from addStudToFile() for whatever reason you should make sure you call
free(myStud.studs);
or you get a memory leak...
Update
All right, going further down, when you fwrite() everything, remember, you malloc()ed the memory for studs. sizeof(studlist) is computed at compile time and can't possibly know of the additional memory used at run time. In addition, the two memory regions are not guaranteed to be continuous, so still, one fwrite wouldn't cut it.
With your code structured as it is, you would be better off fwrite()ing the studCtr first, then the memory you malloced for studs.
For the displayStudsFromFile() since there is just a loop there and nothing is really stored for later, I'd just use
student myStud;
ie, use just one instance of student structure instead of a studlist. In this scenario you do one fread() to read in the studCtr from the disk file and then use that to loop around fread() for one student object at a time into myStud. Within that loop you print the fields of interest like so:
printf("borrowed %d books", myStud.bksCtr);
Hope this will get you going... First steps in C are a bit tough :D

myStud.studs is a pointer to a student, but I don't see where you're actually allocating that student. You need to malloc a student before you can do things like &myStud.studs->idNo.

In short, don't write pointers to files, they will be meaningless later on.
The typical approach is to write out the count of the items first, then loop over each item in the list and write them out individually.
On the reader end:
read the number of items.
Allocate enough memory to hold all the items.
Read each item in.

along with the problems mentioned already,
this function has its' own set of troubles.
I have inserted '<--' and a comment at each problem
fflush(stdin) though works on some implementations, it's still undefined behaviour.
According to the standard, fflush only works with output/update streams
( for your code, since the printf format strings do not end in '\n'
( which would have forced the actual output to occur
( change these lines to 'fflush(stdout)'
A ' ' in a scanf() format string will consume any white space found at that
point in the input. Therefore, for almost all cases, the first char in
the format string should be: ' '. Then newlines, spaces, etc
will be consumed, as if they were never there. It is even correct to
use the leading ' ' when there is no white space to consume.
gets() is depreciated and will corrupt/overrun a input buffer, so NEVER
use gets, rather, use fgets(), where the amount of input can be limited
and similar good things.
void addStudToFile(void)
{
FILE *fp;
studList myStud;
fp = fopen("students.db", "w");
if(fp!=NULL)
{
/* ask for student details and adds these to the file */
printf("Enter ID number: ");
fflush(stdin); <-- change to stdout
scanf(,"%lu", &myStud.studs->idNo);
<-- change format string to: " %lu"
<-- add check of returned value to assure operation successful
printf("Enter First Name: ");
fflush(stdin); <-- change to stdout
gets(myStud.studs->studName.fName);
<-- replace gets with fgets() +appropriate parms)
<-- add check of returned value to assure operation successful
printf("Enter Last Name: ");
fflush(stdin); <-- change to stdout
gets(myStud.studs->studName.lName);
<-- replace gets with fgets() +appropriate parms)
<-- add check of returned value to assure operation successful
printf("Enter Middle Initial: ");
fflush(stdin); <-- change to stdout
scanf("%c", &(myStud.studs->studName.mInitial));
<-- replace format string with " %c"
<-- add check of returned value to assure operation successful
printf("Enter Course: ");
fflush(stdin); <-- change to stdout
gets(myStud.studs->course);
<-- replace gets with fgets() +appropriate parms
<-- add check of returned value to assure operation successful
printf("Enter Year: ");
fflush(stdin); <-- change to stdout
scanf("%d", &(myStud.studs->yrLevel));
<-- change format string to: " %d"
<-- add check of returned value to assure operation successful
fwrite(&myStud, sizeof(studList),1,fp);
<-- add check of returned value to assure operation successful
fclose(fp);
<-- add else clause so use knows what happened. I.E.
} else { perror( "fopen failed for write"); exit(EXIT_FAILURE);
} // end if
} // end function: addStudToFile

Here are my comments, prefixed by '<--'
void displayStudsFromFile(void)
{
FILE *fp;
studList myStud;
fp = fopen("students.db", "r");
if(fp!=NULL)
{
while (fread(&myStud, sizeof(studList), 1, fp))
<-- add check of returned value to assure operation successful
{
printf("%lu\t %s, %s %s\t %s-%d",
myStud.studs->idNo,
myStud.studs->studName.lName,
myStud.studs->studName.fName,
myStud.studs->studName.mInitial,
myStud.studs->course,
myStud.studs->yrLevel);
printf("borrowed %d books", myStud.studs->bksCtr);
}
fclose(fp);
<-- to let user know about error
<-- insert: }else{ perror( "fopen failed for read"); exit(EXIT_FAILURE);
} // end if
} // end function: displayStudsFromFile

Related

C: Loop only prints the last index

I am new to C, so excuse me for asking a seemingly easy question. but I have a loop which loops depending on the user, and inside the loops it asks for a name and an age, however when I go to print, it only prints the last entry and not all the entries I want.
#include <stdio.h>
int main()
{
int size,age;
char name [30];
printf("How long to loop for: ");
scanf("%d", &size);
for (int i=0; i<size; i++)
{
printf("Enter first name: ");
scanf("%s", name);
printf("Enter %s's age: ",name);
scanf("%d",&age);
printf("name: %s, age: %d\n", name,age);
}
return 0;
}
Right now your name and age variables will only hold the last input they received, thats why you're program only prints the last index. You need an array of int as well as an array of char [] to "hold" each input they receive, otherwise those inputs get lost and you only get input for the last index.
your variables should be initialized like:
int size;
printf("Enter size: ");
scanf("%d", &size);
int age [size];
char name[size][30];
in order to get user input and print each entry you will need to access each index by looping through the array size.
to access each index:
age[i]
name[i]
Please Note: using scanf to get a string from user input is generally bad, as it can cause problems if you happen to use a space(such as inputting your full name for example) in your input, and it can cause a buffer overflow if your string is longer than the buffer. Instead you can use fgets, but for your question it isn't necessary, just something to considered in the future in case you want to have spaces in your inputs.
Are you sure that the first scanf actually reads a number.
You want the following to check
if (scanf(" %d", &size) != 1) { // Note space - Eats white space/new lines
printf("Error - invalid size\nExiting\n");
return -1;
}
Prevent buffer overruns
Use:
scanf(" %29s", name); // See above
See point 1 for age
I think this will solve your problems
Okay so if I am understanding you correctly, you are looping through the for loop size number of times and each time you are setting the contents of the variables age and name. It sounds like you are expecting each iteration through the loop to save your entered instance of age and name but this is not the case.
Since you only have one age and one name variable they are going to store whatever the most recently taken input was. So this means whatever was entered on the last loop is what was last written into the variables name and age.
If you then try to print the contents of those variables you are going to see whatever was most recently entered into those variables. you would need more than one name and more than one age variable to write into if you were trying to save multiple entries.
Okay so Anthony, maybe you need to try a little bit of review when it comes to allocating memory. So when we declare the variable
int age;
we are telling the compiler to allocate enough memory to store one single integer that we will call 'age' in our program. since we only asked for enough memory to store one integer, every time you scanf() into the age variable, the single integer is written into that space.
What you want is to store a new integer every time you do another run through your for loop. So, you could declare an array of integers as follows.
int age[20];
Now we have asked the compiler to allocate enough space to store 20 different integers. there will an integer in position age[0] and another integer in age[1] and another in age[2] so on. So now you could use a loop to index through your array and store each entered age from stdin into a different position in the array as follows
for(int i=0; i<20; i++)
{
printf("Please enter your age: ");
scanf("%i", &age[i]);
}
So this loop starts with i=0 and so enters the input from stdin to age[0], the next iteration through the loop has i=1 and so enters the stdin input to age[1] and so on.
Hopefully this clarifies things a bit more?
A major fault is that your printfs will not be displayed as they are buffered.
You need a fflush after them
i.e.
printf("How long to loop for: ");
fflush(stdout);
And
printf("Enter first name: ");
fflush(stdout);
etc...

How can I use feof with while loop?

I want to get student name mid-term and final points and write them in a txt file but when I use a loop, It never get student name. It always gives it a miss. How can I use feof with a loop? I want to get student names mid-term and final point and calculate the average from the got points and It must get always name and points till user press the end of file.
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<conio.h>
void main()
{
FILE *Points;
char namesOfStudents[10];
int pointsOfStudents[1][2];
double AverageOfStudents[1];
int i=0,j=1;
int numberOfStudents;
Points = fopen("C:\\Users\\Toshiba\\Desktop\\PointsOfStudent.txt", "a+");
fprintf(Points, "Name\t\t 1.Grade\t2.Grade\t\tAverage\n");
/* printf("How many students will you enter: ");
scanf("%d",&numberOfStudents);*/
//while (!feof(Points))
printf("Please enter new students name: ");
gets(namesOfStudents);
printf("\nPlease enter new students first point: ");
scanf("%d",&pointsOfStudents[0][0]);
printf("\nPlease enter new students second point: ");
scanf("%d",&pointsOfStudents[0][1]);
for (; i < strlen(namesOfStudents); i++)
{
fprintf(Points, "%c", namesOfStudents[i]); //To write
student name to file
}
fprintf(Points,"\t\t ");
fprintf(Points,"%d\t\t",pointsOfStudents[0][0]); //to write
student's first point
fprintf(Points,"%d\t\t",pointsOfStudents[0][1]); //to write
student's second point
fprintf(Points,"%d\n",(pointsOfStudents[0][0]+pointsOfStudents[0]
[1])/2); //to calculate and write average
system("cls");
fclose(Points);
system("Pause");
}
Several things:
First, NEVER NEVER NEVER NEVER NEVER use gets - it is dangerous, it will introduce a point of failure and/or massive security hole in your code, and it has been removed from the standard library as of the 2011 version of the language standard. Use fgets instead:
fgets( nameOfStudents, sizeof nameOfStudents, stdin );
Secondly, while( !feof( fp ) ) is always wrong. On input from fp, it will loop one time too often. On output to fp, it is meaningless.
You can use the result of fgets to control your loop:
while ( fgets( nameOfStudents, sizeof nameOfStudents, stdin ) )
{
...
}
When you're done entering data from the terminal, signal an EOF using either CtrlZ or CtrlD (depends on your platform).
Third, main returns int, not void; use
int main( void )
instead.
Finally, change
for (; i < strlen(namesOfStudents); i++)
{
fprintf(Points, "%c", namesOfStudents[i]); //To write student name to file
}
to
fprintf( Points, "%s", nameOfStudents );
to write the student name to the file.
There are other problems, but make those changes and see if that doesn't help.

Score storage in file: program crashes on run in c

I want to create a text file using C which would keep on adding score every time the code is run without deleting the last record. Unfortunately, when I run the code, All it does is run the printf statement and create a file score.txt, but doesn't write anything into it, instead it just crashes.
Here's the code:
int main()
{
FILE *score;
score = fopen("score.txt", "w");
fclose(score);
int s;
char n;
printf("You got a high score!\nPlease enter score: ");
scanf("%d", &s);
printf("\nPlease enter your name: ");
scanf("%s", &n);
fprintf(score,"%d", s);
fprintf(score,"%d", n);
printf("\nData Stored into score.txt\n");
return 0;
}
Multiple issues here.
After fclose(score);, you're trying to use fprintf(score,"%d", s);. Why? Maybe you want to move fclose(score); before return 0;
Always put a success check on the return value of fopen(). Also, as per your requirement, keep on adding score every time the code is run without deleting the last record you need to fopen() in append mode. Check more about the modes and their usage here.
scanf("%s", &n); is wrong. Here what you want is an array, not a single char. Consider changing your char n; to char n[32]; or something. [Note: once n is array, change the scanf() to scanf("%s", n);]
fprintf(score,"%d", n); is wrong. Do not use incompatible format specifier. for a string, it should be %s. [Even in your case, n is char. There's no way the format specifier should be %d.]
There are few problems with the code given above.
You need to open the file in append mode, if you need to add the new score without deleting the old one.
You have to write the score to file before doing an fclose.
char n can hold only a single char. If your intention is to read a proper name with more characters, you need a char array, like char name[100].
The code given below gives a proper way to append to a file.
FILE *score;
char name[100];
int nScore;
// Open the file
score = fopen("score.txt", "a+");
if(!score)
{
printf("Failed to open");
return 1;
}
// Get user inputs
printf("You got a high score!\nPlease enter score: ");
scanf("%d", &nScore);
printf("\nPlease enter your name: ");
scanf("%s", name);
//Write to file
fprintf(score, "Name: %s Score: %d\n", name, nScore);
// Close the file
fclose(score);
printf("\nData Stored into score.txt\n");
return 0;
Note also that the name n cannot be stored as a char. It must be either a char [] or a char *. This is probably what's causing your crash, though #SouravGhosh is also correct - basically there are (at least) two bugs in your code.
E.g.
char n[80]; // or any other reasonable value, or learn dynamic memory allocation
scanf("%s", n);

C - 3rd scanf modifies a variable from 2nd scanf

I think I've tried anything (flushing stdin, scanf to consume newline etc.), but nothing works as I had hoped. For some reason a 3rd scanf modifies a variable from 2nd scanf in the following code:
#include <stdio.h>
int main()
{
char first_name[16], last_name[21];
char filename[11];
FILE *opening;
printf("The program saves your first and last name into a file.\n");
printf("Enter your first name:");
scanf("%s", first_name);
getchar();
printf("Enter your last name:");
scanf(" %s", last_name);
getchar();
printf("File where you want to save your name:");
scanf(" %s", filename);
opening = fopen(filename, "wb");
fprintf(opening, "%s %s", first_name, last_name);
printf("\nSuccessfully saved the data!");
fclose(opening);
return 0;
}
The output:
The program saves your first and last name into a file.
Enter your first name: John
Enter your last name: Doe
File where you want to save your name: filename.txt
Successfully saved the data!
All fine and dandy except that the contents of filename.txt is this:
John t
I'm guessing that the 't' character comes from 'txt' somehow, but I've just started learning C and I don't know how to fix this piece of code to work. Could you gurus help me please?
Your filename buffer is too small.
You write filename.txt, which is 12 characters, plus the zero to finish it, makes 13. You only allocate 11. Try like this:
char filename[20];
and it should work.
Be careful though with using scanf, it can lead to very nasty problems, as you are encountering right now. It is good in experimenting and learning C, as it shows you how important correct memory handling is. For any real project you should consider using different functions or frameworks.
Using scanf() on strings is dangerous, as it may read in more data into the buffer than the buffer provides memory.
If scanning in strings one shall always tell scanf() how much characters to read by adding this number to the format passed to scanf():
char file_name[11];
...
scanf("%10s", file_name); /* As file_name provides memor for 11 characters, read a
maximum of 10 characters into file_name leaving 1
character room for the necessary `0-`terminator indicating
the end of the "string". */
Also your code misses error checking on the fopen system call.
Better do something like this:
opening = fopen(filename, "wb");
if (NULL == opening)
{
perror("fopen() failed");
exit(EXIT_FAILURE);
}
If you are entering filename.txt as your file name, then you are overrunning your buffer for filename. That is undefined behaviour and is the cause of the strange results.
To fix, make char filename[11]; larger, remembering to allow 1 extra character for the NULL terminator. In your very specific case, that would be char filename[14]; allowing for the errant space before %s in your scanf call.
Otherwise, all looks fine.

Address booking and writing to file

#include <stdio.h>
#define TRUE 1
#define FALSE 0
typedef struct contact {
char firstname [40];
char lastname [40];
char address [100];
char phone[10];
}contact;
int main ()
{ FILE *pFile;
contact entry = {""};
int choice, firstname_flag = TRUE, lastname_flag = TRUE, address_flag = TRUE, phone_flag = TRUE;
pFile = fopen("C:\\contacts.txt", "a+");
if(!pFile){
printf("File could not be open.");
return 1;
}
do{
printf("Choose a selection:\n\n");
printf("1. First name:\n");
printf("2. Last name:\n");
printf("3. Address:\n");
printf("4. Phone number:\n\n");
scanf( "%d", &choice);
}while((choice < 1 || choice > 4));
switch (choice){
case 1:
firstname_flag = FALSE;
printf("Please enter first name: \n");
scanf("%s", &entry.firstname);
break;
case 2:
lastname_flag = FALSE;
printf("Please enter last name: \n");
scanf("%s", &entry.lastname);
break;
case 3:
address_flag = FALSE;
printf("Please enter address: \n");
scanf("%s", &entry.address);
break;
case 4:
phone_flag = FALSE;
printf("Please enter phone number: \n");
scanf("%s", &entry.phone);
break;
default:
break;
}
printf("\nYou will now be asked to enter the other items. \n\n");
if(firstname_flag){
printf("Please enter first name: \n");
scanf("%s", &entry.firstname);
}
if(lastname_flag){
printf("Please enter last name: \n");
scanf("%s", &entry.lastname);
}
if(address_flag){
printf("Please enter address: \n");
scanf("%s", &entry.address);
}
if(phone_flag){
printf("Please enter phone number: \n");
scanf("%s", &entry.phone);
}
fwrite (here)
fclose(pFile);
getchar();
return 0;
}
Here is the code I have so far. First of all is there anything blatant that pokes out as being invalid or wrong practice etc? 2nd I want to write first name,last name,address, and Phone # to a file. I'm unsure if I need to fwrite "entry" or not. Also, I have noticed when selecting Address first that It's almost like the buffer isn't empty and what I put after the space i.e. 123 Park , Park would be used as the first name wrongly and the next entry I would put in would be Last name. Any suggestions on the code usage and anything at all would be greatly appreciated. Thanks again
1
N° 1. In response to: “invalid or wrong practice.” Some of these are more matters of taste…
If you ‘must’ use scanf, don't point it at a string buffer with %s: specify the length of the buffer with something like %32s to reduce (but perhaps not eliminate) someone from just typing too much and crashing your program (or worse…) — But, Read N°3 below for more on this question…
If you're developing something open-source, or for personal use, GNU readline is a very nice option (but it is GPL not LGPL, so it can't be used in "non-libre" works…)
Your structure initializer doesn't cover all the elements. You could use {"", "", "", ""}
Did you mean fwrite(entry)?
If you're checking for missing fields before writing the file, you might want to leave aside the bank of flags, and instead loop on the "invalid condition."
while ('\0' == entry.firstname[0]) {
printf("Please enter first name: \n");
scanf("%40[^\r\n\0\04]", &entry.firstname[0]);
}
No need to open the file so early, and leave it open while waiting for user interaction. Opening the file in a+ mode is reasonably "dangerous" (see man lockf for the skinny), because someone else might try to write to the file while you are; leaving it open in this mode for a long time increases the risk.
You should probably use strerror(errno) to provide the user the details of a failing system call, such as when checking the return codes of fopen, fwrite, and fclose. Due to the way buffered I/O works, fclose could even be reporting a problem that occurred with fwrite under some circumstances.
Print your error messages to stderr using fprintf (stderr,…); instead of the output stream …
If you are going to use flags to indicate that the data is "valid" (or, at least, that it may be), you should probably do so after the user tries to enter it, instead of before.
Functions are your friends … I would probably break up something like this by using a function to collect the user's input (something like prompt_for_field ("first name", &entry.firstname);), a function to check for missing records and prompt for them, and a function to write the record to the file, at least …
It's usually considered good form to exit from main, rather than return, for example by using exit(EXIT_SUCCESS)/exit(EXIT_FAILURE). I believe the idea is to support esoteric and possible extinct systems who might treat some value(s) other than 0 as a successful status code (VMS, perhaps?), but regardless, it's easier to read :-)
2
And N°2, yes, you can fwrite(entry), and as long as you never change the definition of struct contact, you should be able to read it back in all right. Over time, you'll probably want to switch to a more "plain text" type format (#include <json-xml-init-religious-war>), but in this small example, there isn't a pressing need to introduce such complexity.
3
Finally, N°3, you should probably use &entry.address[0] to get the address of the start of a char[] but most significantly: scanf %s does not read a string. It looks like printf %s, but it's not…
s Matches a sequence of non-white-space characters; the next
pointer must be a pointer to character
array that is long enough to hold the input sequence and the terminating null character ('\0'),
which is added automatically. The input string stops at white space or at the maximum field width,
whichever occurs first.
See that "non-white-space?" That's what's got you. The rest of your input (after the first whitespace) is left in the keyboard buffer, waiting for another scanf to read it. A good('ish) way to read from the terminal and accept whitespace is %40[^\r\n\0\04], replacing 40 with the size of your string buffer (char[]). That means, accept (up to) 40 characters, as long as they are none of: carriage return, new line, the null byte, or end-of-file code (^D).
All-in-all, you look to be on the right track, though. Good luck :-)

Resources