Part one showed you how to open/read from/write to and close files. This issue will deal with more complex functions and algorithms.
File-Error Handling
The function int ferror(FILE *fp); returns true if the last operation performed on fp caused an error, otherwise it returns false.
An example on how to use ferror():
ch=getc(fp);
if(ferror(fp)) f_err_report(ERR_READ);
getc(fp) reads a char from the file associated to fp. If an error occured with getc(fp), ferror(fp) would return true and call f_err_report(ERR_READ); (which reports the error to stdout and exits the program).
Full-Source:
1: // file error handling
2: #include <stdio.h>
3:
4: #define ERR_OPEN_1 11
5: #define ERR_OPEN_2 12
6: #define ERR_READ 13
7: #define ERR_WRITE 14
8:
9: int f_err_report(int mode) {
10: if(mode==ERR_OPEN_1) printf("error opening file1n");
11: if(mode==ERR_OPEN_2) printf("error opening file2n");
12: else if(mode==ERR_READ) printf("error reading from file");
13: else if(mode==ERR_WRITE) printf("error writing to file");
14: exit(1);
15: }
16:
17: int main(int argc, char *argv[])
18: {
19: if(argc<3) { printf("%s <file1> <file2>n",argv[0]); exit(1); }
20: char ch;
21: FILE *fp,*fp2;
22: if((fp=fopen(argv[1],"r"))==NULL) f_err_report(ERR_OPEN_1);
23: if((fp2=fopen(argv[2],"w"))==NULL) f_err_report(ERR_OPEN_2);
24: while(!feof(fp)) {
25: ch=getc(fp);
26: if(ferror(fp)) f_err_report(ERR_READ);
27: if(!feof(fp))
28: putc(ch,fp2);
29: else break;
30: if(ferror(fp)) f_err_report(ERR_WRITE);
31: }
32: fclose(fp);
33: fclose(fp2);
34: return 0;
35: }
36:
Youre strongly encouraged to use ferror() whenever you perform operations on a FILE pointer, since errors that "could" arise from such a source may hardly be identified if you dont report them.
Removing files from the filesystem
Deleting files is done useing int remove(const char *filename); which returns false on success or int>0 on errors.
Since remove() returns 0 on success an if-statement suffices to do the "error-checking".
An example on how to use remove():
if(remove(argv[1])) { printf("error deleting file: %sn",argv[1]); exit(1); }
In the above example the function remove() is called with a pointer to the first argument given to the program as a parameter. If it does not return false, the error is reported and the program terminates.
Full-Source:
1: // deleting a file
2: #include <stdio.h>
3:
4: int main(int argc, char *argv[])
5: {
6: if(argc<2) { printf("%s <filename>n",argv[0]); exit(1); }
7: char ch;
8: printf("are you sure you want to delete regular file %s? (y/n): ",argv[1]);
9: ch=getc(stdin);
10: if(ch=='y') {
11: if(remove(argv[1])) { printf("error deleting file: %sn",argv[1]); exit(1); }
12: } else printf("abortedn");
13: return 0;
14: }
15:
fread() & fwrite()
To read in datatypes with a size of more than 1 byte from a binary file (must be opened in binary access-mode), the C-Standard supplies the fread() and fwrite() functions.
fread()
size_t fread(void *ptr, size_t num_bytes, size_t count, FILE *stream);
This function reads count elements of size num_bytes from a FILE * (filepointer) stream, and copies it to the memory as pointed to by ptr.
fwrite()
fwrite() works just the other way around, meaning ptr is the place in memory from where to copy count elements of size num_bytes to a FILE * stream.
size_t fwrite(const void *ptr,size_t num_bytes,size_t count,FILE *stream);
An example on how to use fread() & fwrite():
if(fread(p,sizeof(char)*5,1,fp)!=1) {
if(feof(fp)) break;
f_err_report(ERR_READ);
}
if(fwrite(p,sizeof(char)*5,1,fp2)!=1) {
if(feof(fp)) break;
f_err_report(ERR_WRITE);
}
The above code reads 1 array of 5 chars from the FILE * fp and copies it to memory starting at p and vicaverse (1 times the size of 5 char of p to fp2. feof() is called because both functions return 1 on failure and EOF (End of File), and since we want to know if the operation failed or just hit the end of the file we check before "creating" an error.
Full-Source:
1: // fread & fwrite example
2: #include <stdio.h>
3:
4: #define ERR_OPEN_1 11
5: #define ERR_OPEN_2 12
6: #define ERR_READ 13
7: #define ERR_WRITE 14
8:
9: #define TEXTSIZE 100
10:
11: int f_err_report(int mode) {
12: //...
13: }
14:
15: int main(int argc, char *argv[])
16: {
17: if(argc<3) { printf("%s <file1> <file2>n",argv[0]); exit(1); }
18: char *p;
19: p=(char *)malloc(sizeof(char)*TEXTSIZE);
20: FILE *fp,*fp2;
21: if((fp=fopen(argv[1],"r"))==NULL) f_err_report(ERR_OPEN_1);
22: if((fp2=fopen(argv[2],"w"))==NULL) f_err_report(ERR_OPEN_2);
23: while(!feof(fp)) {
24: if(fread(p,sizeof(char)*5,1,fp)!=1) {
25: if(feof(fp)) break;
26: f_err_report(ERR_READ);
27: }
28: if(fwrite(p,sizeof(char)*5,1,fp2)!=1) {
29: if(feof(fp)) break;
30: f_err_report(ERR_WRITE);
31: }
32: }
33: fclose(fp);
34: fclose(fp2);
35: return 0;
36: }
37:
Getting the filepointers position indicator
If you open up a file-descriptor (FILE * or filepointer) in C, a stream is created inside the memory, containing the first some bytes of the file. If an operation is performed on that FILE *, the position-indicator is beeing increased, which copies some more bytes from the file to memory (inside the stream you opened. (this happens in background)). The following operation will therefore automatically read from a "later" position inside the file and also move the position indicator towards the end of the file.
To find the (memory-)address of the current FILE *'s (filepointer's) position, the function long ftell(FILE *fp) is called, returning the adress of the current position inside the file associated to fp or -1 on error.
Moving the filepointers position indicator
In cases one needs to find a certain position in a file or just skip some bytes that wont be needed, it is possible to move the FILE *'s postition indicator (The pointers position in the file).
This is done useing the fseek() function, which is defined as: int fseek(FILE *fp,long numbytes,int origin); where fp is a valid FILE * (filepointer), numbytes the number of bytes to "skip" and origin the offset from which to start seeking.
fseek() returns 0 on success or (int)i!=0 on errors. origin can be one of the following macros:
SEEK_SET - marks the beginning of a file
SEEK_CUR - marks the current position in a file
SEEK_END - marks the end of a file
An example on how to use fseek():
fseek(fp,sizeof(char),SEEK_SET); // find the 2nd byte of file1
The above example (dont miss the link(s)) shows the use of fseek() inside an application. sizeof(char) is 1 and since SEEK_SET makes fseek() start from the beginning of a file, the above line skips the first byte of the file pointed to by fp
Full-Source:
1: // fseek example (based on "file error handling" f_err_report.c)
2: // prints every second char of a file1 to file2, starting with the 2nd char of file1
3: #include <stdio.h>
4:
5: #define ERR_OPEN_1 11
6: #define ERR_OPEN_2 12
7: #define ERR_READ 13
8: #define ERR_WRITE 14
9: #define ERR_SEEK 15
10:
11: int f_err_report(int mode) {
12: if(mode==ERR_OPEN_1) fprintf(stderr,"error opening file1n");
13: if(mode==ERR_OPEN_2) fprintf(stderr,"error opening file2n");
14: else if(mode==ERR_READ) fprintf(stderr,"error reading from file");
15: else if(mode==ERR_WRITE) fprintf(stderr,"error writing to file");
16: else if(mode==ERR_SEEK) fprintf(stderr,"error looking up a position");
17: exit(1);
18: }
19:
20: int main(int argc, char *argv[])
21: {
22: freopen("ERRORS","w",stderr);
23: if(argc<3) { printf("%s <file1> <file2>n",argv[0]); exit(1); }
24: char ch;
25: FILE *fp,*fp2;
26: if((fp=fopen(argv[1],"rb"))==NULL) f_err_report(ERR_OPEN_1);
27: if((fp2=fopen(argv[2],"w"))==NULL) f_err_report(ERR_OPEN_2);
28: fseek(fp,sizeof(char),SEEK_SET); // find the 2nd byte of file1
29: while(!feof(fp)) {
30: ch=getc(fp);
31: if(feof(fp)) break;
32: fseek(fp,sizeof(char),SEEK_CUR); // skip 1 byte-sizeof(char)
33: if(feof(fp)) break;
34: if(ferror(fp)) f_err_report(ERR_READ);
35: if(!feof(fp))
36: putc(ch,fp2);
37: else break;
38: if(ferror(fp)) f_err_report(ERR_WRITE);
39: }
40: fclose(fp);
41: fclose(fp2);
42: return 0;
43: }
44:
Basically fseek should only be used with binary-streams, since characterconversion is used when handling plaintext files, which can lead to differences between the byte inside the file and the one youre looking for.
Be aware that this does not mean that you cannot use fseek with a plaintext-file, but that it needs to be accessed/opened in binary mode. There is nothing that prevents or speaks against opening a plaintextfile in binary-mode. It just means that the previously mentioned character-conversion routines will not be applied.
Rewinding the Position-Indicator
The function rewind() sets the files position-indicator back to the beginning of the file. This is an equivalent to closeing and reopening a file which would look a bit strange, wouldnt it? :)
The functions definition is: void rewind(FILE *);, where fp is a valid filepointer thats to be "rewinded".
Standard-Streams
Quote: With C, everything from an on-disc file to a printer is a file.
Therefore the screen(s) and keyboard(s) are "files" too. (even if windows might make you tend to believe otherwise).
This also means that every program must open these "files", since you might want to receive user input and maybe also give the user some information about whats happening. Therefore every program opens 3 streams (file descriptors (filepointers that point to a fopen()'ed file)), which are the Screen, the Keyboard and the error console (mostly the screen too). These streams are called "Standard Streams" since every program uses them.
The Standard-Streams:
stdin the Input Device (keyboard)
stdout the Output Device (screen)
stderr the Error Console (typicaly redirected to stdout)
Redirect Standard Streams
It is possible to redirect the Standard Streams to files, which opens some very fancy prospects for user I/O.
i.e. one could relay all errors (normally sent to the screen through stderr) to a file called "ERRORS.mine". This would be done through the function freopen() which is defined as:
FILE *freopen(char *filename,const char *mode,FILE *stream);
filename is the name of the file the stream gets redirected to. mode is a valid file-opening-mode as described in the prior issue of this article C File Handling Basics, and stream is a valid (fopen()'ed stream). It returns a FILE * (filepointer) to a stream associated to the file filename or NULL on failure. Attend that in fact freopen() redirects any stream (not just the Standard-Streams) of type "FILE *". (which can be held as proof that the Standard-Streams are simple FILE *'s (filepointer's).)
An example on how to use freopen():
freopen("ERRORS","w",stderr);
The above example redirects any error reported through stderr to a file named ERRORS. (which makes the errors not show up on the screen but inside a file... ;)
Full-Source:
1: // file error handling
2: #include <stdio.h>
3:
4: #define ERR_OPEN_1 11
5: #define ERR_OPEN_2 12
6: #define ERR_READ 13
7: #define ERR_WRITE 14
8:
9: int f_err_report(int mode) {
10: //...
11: }
12:
13: int main(int argc, char *argv[])
14: {
15: freopen("ERRORS","w",stderr);
16: if(argc<3) { printf("%s <file1> <file2>n",argv[0]); exit(1); }
17: char ch;
18: FILE *fp,*fp2;
19: if((fp=fopen(argv[1],"r"))==NULL) f_err_report(ERR_OPEN_1);
20: if((fp2=fopen(argv[2],"w"))==NULL) f_err_report(ERR_OPEN_2);
21: while(!feof(fp)) {
22: ch=getc(fp);
23: if(ferror(fp)) f_err_report(ERR_READ);
24: if(!feof(fp))
25: putc(ch,fp2);
26: else break;
27: if(ferror(fp)) f_err_report(ERR_WRITE);
28: }
29: fclose(fp);
30: fclose(fp2);
31: return 0;
32: }
33:
I hope you found these File-Handling Articles as useful as i enjoyed it writing them; Feel free to (ab)use any of the above code as your personal skeletons, blowrag or whatever... (its a gift :) |