File Handling

File Handling In C

Requirements:
Linux Distribution
C Compiler
a shell interface
My Setup:
Debian GNU/Linux
GCC
BASH

Today we will go over the concept of file handling. Files are essential for
persistent storage. So inside of the standard input / output header file
(stdio.h), we have multiple functions for handling files. The FILE data structure
is implemented differently between operating systems and userlands. This is how
my FILE structure looks like on Debian. It is found in
usr/bits/types/struct_FILE.h:

/* Copyright (C) 1991-2018 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */

#ifndef __struct_FILE_defined
#define __struct_FILE_defined 1

/* Caution: The contents of this file are not part of the official
   stdio.h API.  However, much of it is part of the official *binary*
   interface, and therefore cannot be changed.  */

#if defined _IO_USE_OLD_IO_FILE && !defined _LIBC
# error "_IO_USE_OLD_IO_FILE should only be defined when building libc itself"
#endif

#if defined _IO_lock_t_defined && !defined _LIBC
# error "_IO_lock_t_defined should only be defined when building libc itself"
#endif

#include <bits/types.h>

struct _IO_FILE;
struct _IO_marker;
struct _IO_codecvt;
struct _IO_wide_data;

/* During the build of glibc itself, _IO_lock_t will already have been
   defined by internal headers.  */
#ifndef _IO_lock_t_defined
typedef void _IO_lock_t;
#endif

/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
  __off64_t _offset;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};

/* These macros are used by bits/stdio.h and internal headers.  */
#define __getc_unlocked_body(_fp)					\
  (__glibc_unlikely ((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end)	\
   ? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++)

#define __putc_unlocked_body(_ch, _fp)					\
  (__glibc_unlikely ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end)	\
   ? __overflow (_fp, (unsigned char) (_ch))				\
   : (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))

#define _IO_EOF_SEEN 0x0010
#define __feof_unlocked_body(_fp) (((_fp)->_flags & _IO_EOF_SEEN) != 0)

#define _IO_ERR_SEEN 0x0020
#define __ferror_unlocked_body(_fp) (((_fp)->_flags & _IO_ERR_SEEN) != 0)

#define _IO_USER_LOCK 0x8000
/* Many more flag bits are defined internally.  */

#endif

Usually you will not see that structures internals , you just pass around a
pointer to a file and the runtime deals with the abstraction, however knowing
this could be useful. The main functions i will be focusing today are:


FILE * fopen( const char * filePath, const char * mode );	//opens new or existing file

int fprintf(FILE *stream, const char *format, ...); //write data into file

int fscanf(FILE *stream, const char *format, ...);  //reads data from file

int fputc(int char, FILE *stream);   //writes a character into file

int fgetc(FILE *stream);  //reads a character from file

char *fgets(char *str, int n, FILE *stream);    //Reads a line from the specified stream and 
                                                //stores it into the string pointed to by str. 
                                                //It stops when either (n-1) characters are read, 
                                                //the newline character is read, or the end-of-file 
                                                //is reached, whichever comes first.

int fclose( FILE * stream );	//closes the file

int fseek(FILE *stream, long int offset, int whence);	//sets the file pointer to given position
                                                      //offset is how far in bytes to seek from 
                                                      //the whence

nt ferror(FILE *stream); //Tests the error indicator for the given stream.

int feof(FILE *stream); //Tests the end-of-file indicator for the given stream.

long int ftell(FILE *stream); //returns current position

void rewind(FILE *stream);	//sets the file pointer to the beginning of the file

size_t fwrite(const void *ptr, size_t size,              
              size_t nmemb, FILE *stream);  //Writes data from the array pointed to by ptr to the given stream.

size_t fread(void *ptr, size_t size, 
            size_t nmemb, FILE *stream);  //Reads data from the given stream into the array pointed to by ptr.

When you create a file, you can enter one of six modes. This will indicate the
type of actions you plan on doing with said file. Below is a list of modes which
you can state during opening a file:

/* 1.r	: Opens an existing text file.
   2.w	: Opens a text file for writing if the file doesn't exist then a new 
          file is created.
   3.a	: Opens a text file for appending(writing at the end of existing file) and 
          create the file if it does not exist.
   4.r+	: Opens a text file for reading and writing.
   5.w+	: Open for reading and writing and create the file if it does not exist. 
          If the file exists then make it blank.
   6.a+	: Open for reading and appending and create the file if it does not exist. 
          The reading will start from the beginning, but writing can only be 
          appended.
          */

After you open a file, you have multiple options based on your mode. If you
are reading a file, you could read via fgetc, fgets, fread, or fscanf, if you
wanted to write to a file, you could use fputc, fprintf, or fwrite. When you need
to figure out where you currently are in a file , so you dont overwrite any data,
you can use the ftell function to determine where you are, and say you do need
to start from the top again, you can call rewind. When it comes to the fseek
function, it is normal to pass one of the 3 macros declared within the stdio.h
file:

1: SEEK_SET – Beginning of file
2: SEEK_CUR – Current position of the file pointer
3: SEEK_END – End of file

This is useful if you need to change a variable in a function, and you know
where it is from one of those points. because then you can set the current position
of the stream to the point based on where you were, and then overwrite whatever
data you need.

Some Examples:


//Reading and writing 
#include <stdio.h>
#include <stdlib.h>

int main () {
   FILE * fp;

   fp = fopen ("file.txt", "w+");
   fprintf(fp, "%s ","Sending A Hello To a File!");
   fclose(fp);
   int c;
   fp = fopen("file.txt", "r");
    do {
      c = fgetc(fp);
      if(feof(fp)) 
         break ;

      printf("%c", c);
   }while(1);
   fclose(fp);
      printf("\n");

   return 0;
}

//fseek example:
#include <stdio.h>

int main () {
   FILE *fp;

   fp = fopen("file.txt","w+");
   fputs("Hello World!\n", fp);

   fseek( fp, 6, SEEK_SET );
   fputs(" User", fp);
   fclose(fp);

   return(0);
}

 

Leave a Reply