CSPP 50101 - Lab 6

In this Lab: Lab 6 is due: Saturday, Aug 6 at 11:59pm

Files for this lab are located in: /stage/classes/archive/2011/summer/50101-1/lab/lab6/src

Header files and code reuse

In previous labs, you have written a single .c file for every program that you have created. This means that that if you want to reuse a function in another program, then you must copy that function into a new source file. In this lab, you will organize your source code into multiple files so that code reuse is easier.

This lab contains a simple module of some file processing functions. The code for these function is organized into two different files:

First look at the file myfileio.h, which has a declaration of function show_file():
  int show_file(char * filename);
The body of show_file() (this function is discussed below) is in myfileio.c.

The file demo.c has a function main() that calls and tests show_file():

int main(int argc, char ** argv)
{
  if (argc != 2)
  {
    printf("Usage: %s filename\n", argv[0]);
    exit(1);
  }

  show_file(argv[1]);

  return 0;
}
How do we call a function from main() that is implemented in a different file? First, demo.c includes this preprocessor directive, so that the function declarations in myfileio.h are available for the compiler:
  #include "myfileio.h"
Quotation marks are used instead of brackets ( < > ) to tell the compiler to look in the current directory for myfileio.h.

Next, to compile code in myfileio.c and demo.c into a single executable, we include both files as input to gcc. (You do not need to include .h header files as input to gcc.)

$ gcc -Wall -std=c99 myfileio.c demo.c -o demo
Then, run this program using the file in1.txt as input:
$ ./demo in1.txt
Calling show_file() ...
100
110
120
125
130
You can see that running this program calls the function main() in demo.c which then calls the function show_file() in myfileio.c.

Preventing multiple inclusions of a header file

In some cases, including a header file more than once in the same program will cause a compiler error. To prevent this, we use the ifndef preprocessor directive to prevent multiple inclusions of the header:
 
   #ifndef MYFILEIO_H 
   #define MYFILEIO_H

   /* Print contents of file to stdout. 
    * Returns -1 if error occurs, 0 otherwise */
   int show_file(char * filename);
 
   ...
 
   #endif
Here, the directive ifndef checks if MYFILEIO_H has already been defined, in which case the body of this directive is not included in compilation.

File I/O

In Lab 4 you saw the program echo.c that echos characters from standard input to standard output using function getchar() and putchar(). The myfileio module (myfileio.h and myfileio.c) contain a function that performs similar steps, but rather than reading from standard input it reads from a file on disk. Look at the code for show_file() in myfileio.c:
int show_file(char * filename)
{
  FILE * fp;
  char ch;

  if( (fp=fopen(filename,"r")) == NULL)
    return -1;

  while ((ch=getc(fp)) != EOF)
    putchar(ch);

  fclose(fp);

  return 0;
}
First, notice that the call to fopen(), which opens a file on disk for reading (indicated by argument "r" ):
  if( (fp=fopen(argv[1],"r")) == NULL)
    return -1;
fopen() returns what we call a file pointer which is a pointer to a structure (or block of memory) that stores data the the program needs about this file. The program then reads characters from the file with calls to getc(), which takes a file pointer as an argument:
  while ((ch=getc(fp)) != EOF)
    putchar(ch);  
In the previous program echo.c we used the function getchar() instead of getc(). Note that getchar() always reads from standard input (and so no file pointer is passed in as an argument) while getc() is used to read from an arbitrary file stream.

Exercise 1

Add a function to myfileio.c that counts the number of lines in a file. The name of the file is passed in as a function argument. You can assume that the last character in the file is '\n', that is, that every line ends with a newline character.

You are already provided with this function prototype in myfileio.h:

  /* Returns count of lines in file, or -1 if error occurs */
  int count_lines(char * filename);
You only need to implement this function in myfileio.c.

You can use file test_ex1.c to test your implementation of count_lines(). This program reads the name of an input file from the command line and passes this as an argument to count_lines(). Then the return value of count_lines() is displayed. You can use files in1.txt and in2.txt as sample input files.

You do not need to make any changes to test_ex1.c; it should work as-is with your version of count_lines().

Remember to compile both test_ex1.c and myfileio.c into a single executable:

$ gcc -Wall -std=c99 myfileio.c test_ex1.c -o test_ex1
To test your results, compare output with the command wc -l, which counts the number of lines in a file:
$ ./test_ex1 in1.txt
Number of lines in in1.txt: 5
$ wc -l in1.txt
       5 in1.txt
In the above example you can see that both wc -l and test_ex1 show that in1.txt contains 5 lines.

Reading from a file with fprintf()

In the function show_file() the function getc() is used to read a single character from the input file pointer. You can use the function fscanf() to read other data types from an open file. The function fscanf() is similar to scanf() with the exception that data is read from a file pointer passed as an argument rather than from standard input. Like scanf(), you use conversion specifiers to indicate the data type to be read.

The file fscanfdemo.c shows an example of a call to fscanf(). Here an integer is read from an open file:

  int val;
  fscanf(fp,"%d",&val);
Again note that this program calls fopen() and fclose(), to open and close a file pointer of a file written to disk.

You can read more about fscanf in Chapter 13 of Prata.

Exercise 2

Implement a function called get_values() that reads n integer values from a file stream, and returns the result in a dynamically allocated array. Your function should call malloc() to allocate space for this array, read data into this array with scanf, and then return a pointer to the new array.

get_values() should not call free(); you can assume that the calling function will do then when it not longer needs the array that is returned.

get_values() should return NULL if a malloc() error occurs. If a scanf() error occurs, the function should return NULL and free any memory that it has already allocated with malloc (otherwise you may have a memory leak).

You are given this function prototype in myfileio.h:

/* Reads n integer values from filename, returns result in dynamically 
 * allocated array, or returns NULL if error occurs */
int * get_values(char * filename, unsigned int n);
As with Exercise 1, you should add your implementation of get_values() to myfileio.c.

Use the file test_ex2.c to test your implementation of get_values(). In this program, the function main() uses count_lines() from Exercise 1 to compute n, the number of lines in the input file. Then, get_values() is called to read n integers from the input file.

You can use input files in1.txt to test your program:

$ ./test_ex2 in1.txt
Number of lines in in1.txt: 5
Printing values in in1.txt ...
100
110
120
125
130

Writing output to a file

The file writedemo.c contains a very simple program that creates a new file with the contents "Hello cspp50101!". You can use the command cat to quickly the contents of the output file write.txt:
gcc -Wall -std=c99 writedemo.c -o writedemo
$ ./write
Writing to file write.txt ...
$ cat write.txt
Hello cspp50101!
Take a look at the program writedemo.c. You can see that this program also calls fopen() and fclose() before and after using the file pointer fp.
int main(int argc, char ** argv)
{
  FILE * fp;

  if( (fp=fopen("write.txt","w")) == NULL)
  {
    printf("Can't open file write.txt for writing.\n");
    exit(1);
  }

  printf("Writing to file write.txt ...\n");

  fprintf(fp,"Hello cspp50101!\n");

  fclose(fp);

  return 0;
}
This program also uses "w" as the second argument to fopen(), to indicate that this file is opened for writing (and not reading).

Exercise 3

Add another function to myfileio.c called write_columns() that writes data in an array of integers to a file. The array of data and the name of the file passed as a function argument.

As with the previous exercises, you are given this function prototype in myfileio.h, and you only need to update myfileio.c with your implementation of this function:

/* Writes n values in arr to new file in 2 columns.
 * Returns -1 if error occurs, 0 otherwise */
int write_columns(int * arr, unsigned int n, char * filename);
Data will be written in 2 columns separated by a tab. So the first n/2 values will be in the first column, and the rest will be in the second column. If the length of the input array is odd then the first (n+1)/2 values should be in the first column.

You are given the file test_ex3.c to test your implementation of write_columns(). This program takes the name of the input and output files as command line arguments:

$ gcc -Wall -std=c99 myfileio.c test_ex3.c -o test_ex3
$ ./test_ex3 in1.txt out1.txt 
Number of lines in in1.txt: 5
Displaying output file out1.txt...
100	125
110	130
120
Again you should only modify file myfileio.c by adding your implementation of this function.

Submitting your assignment

Lab 6 is due: Saturday, Aug 6 at 11:59pm

For this lab you only need to submit file myfileio.c, which should have implementations of these functions:

It's fine if you include other *.c files in your submission, but I will grade using the versions provided for this lab.

Be sure that every source file you submit includes this information in comments at the top of the file:

Also include a simple README file in your lab6 directory. This file should include your first and last name, your username, the list of files that you are submitting, and any other comments or questions for the Lab TA.

Do not submit any compiled binary files

You will submit this lab with the hwsubmit program.

$ hwsubmit cspp50101lab <path to your lab6 directory>
Submit to cspp50101lab, NOT cspp50101.

prepared by Sonjia Waxmonsky