Never confuse education with intelligence, you can have a PhD and still be an idiot.
- Richard Feynman -



Chapter:Error handling in C

From Juneday education
Jump to: navigation, search


Meta information about this chapter

Expand using link to the right to see the full content.

Introduction

If there were no possible failures writing software would be a lot easier. But things may happen. Fact is things will happen. Dealing with failures or unplanned behaviours is part of programming. In this chapter we will show you some of the things that may go wrong but the focus will be on how to write robust code - code that handles failures in a good and structured way.

We will also look a bit into signals in C

Purpose

Making the student able to write robust programs.

Requirements

The student shall be familiar with the following concepts:

  • function
  • return values
  • pointers (is good, but not required)

Goal

The student shall:

  • understand the importance of checking the return value
  • be able to write functions handling failures
  • write a signal handler

Instructions to the teacher

Common problems

Chapter videos

All videos in this chapter:

See below for individual links to the videos.

Checking return value

Functions in C typically return a value indicating if all worked (usually indicated by returning 0) or if something failed. In this section we're going to briefly talk about the theory and move on to look at some practical examples of when it's good, or even crucial, to checkc the return values and act on them.

Description

We've seen in the chapter about functions that a function can return a value. It is quite often the case that a C function returns a value to indicate the status of the operaration rather than returning a value. We will in this section look into how to practically use the return values to make a more robust program.

Videos

  1. Error handling in C - pt I (eng) (pdf)
  2. Error handling in C - pt II (eng) (same pdf as above)
  3. Error handling in C - pt III (eng) (same pdf as above)
  4. Error handling in C - gets (don't use gets!) (eng) (live coding video) gets-example.c
  5. Error handling in C - atoi (don't use atoi!) (eng) (live coding video) atoi-example.c
  6. Error handling in C - using scanf properly (eng) (live coding video) scanf-example.c
  7. Error handling in C - printf and return values (eng) (live coding video) printf-example.c
  8. Error handling in C - printing the content of a file (poorly) (eng) (live coding video) print-textfile-bad.c
  9. Error handling in C - printing the content of a file (better) (eng) (live coding video) print-textfile.c

Exercises

Converting a string to an int using atoi

Write a program that creates three string (char*) contaning "Hello", "3" and "0". Convert these strings to int using atoi. Make sure to check wether atoi managed to convert the strings into integers.

Hint: you can't do this. So this is a rather silly exercise. The motivation behind this exercise is that you should see with your own code that atoi is not a wise choice.

Read data from stdin using gets

Write a program that creates a buffer of 10 char, char buf[10];. Use this buffer and gets to read from stdin. Printf out the content of the buffer using printf.

Run the program and type some five letters or so and then press enter.

Run the program and type at least 100 letters and then press enter. It should now crash. Why?

Hint: Apart from in this exercise gets should really not to be used....we think you'll learn it this way :).

scanning data from a log file

Given one of the author's background we give you a couple of exercises based around reading a log file.

Your task is to write a piece of software that reads data from a log file. The data consists of multiple lines of two integers separated by a semicolon (";"). An example of log file:

12;23
12;34
12;35
12;34
12;34
12;34
12;34
12;34
12;34
12;34
12;34
17;45
12;34
12;34
12;34

You know that the data is at most an integer with two digits, ";" and an integer with at most two digits followed by "\0", This means that a buffer of 6 character should be enough. Use that buf and fgets to read data from a file. The data shall be stored in two integers variables and printed immediately.

Your program should read a log file. How do we tell your program what file to read? Let's do it the same way as we tell gcc to compile a file. That is using the command line. In short you can add the following to your main function:

int
main (int argc, char** argv)
{
  char* file_name;

  /* Make sure we have enough arguments */
  if (argc<2)
    {
      fprintf(stderr, "Missing file argument\n");
      return 1;
    }

  file_name = argv[1];

In the code above we make sure we have 2 arguments (the program name itself plus the log file to read). If all that is well we continue by storing the file name (passed by the user to the program) in a string called, file_name.


The log file above can be found at: good-2016-11-17.log.

To run the program (assuming it is called read-log:

./read-log  good-2016-11-17.log

scanning data from a new log file

All of a sudden the program creating the log files have been released in a new version. This version prints out three integers followed by some text. We have a log file created by this new version of the pogram. Your task now is simply to run the program using the program you wrote in the previous exercise.

A log file prouced by the new program looks like this:

12;23;13
12;34;13
12;35;13
12;34;13
12;34;13
12;34;13
12;34;13
12;34;13
12;34;13
12;34;13
12;34;13
17;45;13
12;34;13
12;34;13
12;34;13


To run the program (assuming it is called read-log:

./read-log  new-2016-11-17.log

The log file above can be found at: new-2016-11-17.log.

.... given that the buffer size (used in fgets) is 8 characters you can't fgets the entire line into your buffer. This means there will be some remaining charcters to read in before you read the next line. This causes a bug in your program.

scanning data from a new log file a bit better

Rewrite your code to handle that a line may be longer that the 6 characters. You can assume the the length of each line is 9 characters. Make sure to handle:

  • the entire line in the file is read
  • the scanning succeeds

If a problem occurs, print a message to stderr and exit (with 1 as return value). We suggest you simply read a line (same amount of characters as before) and scan the line. If you fail scanning and converting (check the return value) 2 integers we have a clear indication of the file being corrupt, so return from the function read_print_file with an error code indicating that scan failed..

Test your code using this example log file with incorrect lines in it.

12;23;13
12;34;13
12;35;13
12;34;13
34;13
12;34;13;test data....this line is really corrupt
12;34;13
12;34;13
12;34;13
12;34;13
a;34;13
17;45;13
12;34;13
12;b;13
12;34;13


The log file above can be found at: corrupt-2016-11-17.log.

Solutions

Expand using link to the right to see the full content.

Converting a string to an int using atoi

#include <stdio.h>
#include <stdlib.h>

static void
print_str_and_int(char *str)
{
  printf ("%20s as an int: %d\n",
          str, atoi(str)); 

}

int
main(void)
{
  print_str_and_int("Hello");
  print_str_and_int("3");
  print_str_and_int("0");

  return 0;
}


Read data from stdin using gets

#include <stdio.h>

char *gets(char *s);

int main(void)
{
  char buf[10];

  gets(buf);
  printf ("buff: %s\n", buf);

  return 0;
}

scanning data from a log file

Suggested solution:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


#define READ_BUF_SIZE 6

enum
  {
    OK,
    FAILED_OPEN_FILE,
    SCANF_FAILURE,
    CLOSE_FAILURE
  } return_values;


int
read_print_file(char * file_name)
{
  char buf[READ_BUF_SIZE];
  FILE* fp;
  int x;
  int y;
  
  fp = fopen (file_name,"r");
  if (fp == NULL)
    {
      return FAILED_OPEN_FILE;
    }

  while (fgets(buf, READ_BUF_SIZE, fp))
    {
      sscanf(buf, "%d;%d", &x, &y);
      printf ("x:%d  y:%d\n", x, y);
      
    }
  if (fclose (fp))
    {
      return CLOSE_FAILURE;
    }

  return OK;
}

int
main (int argc, char** argv)
{
  char* file_name;
  int ret;
  
  /* Make sure we have enough arguments */
  if (argc<2)
    {
      fprintf(stderr, "Missing file argument\n");
      return 1;
    }

  file_name = argv[1];
  ret = read_print_file(file_name);
  if (ret)
    {
      if (ret==FAILED_OPEN_FILE)
        {
          printf( "Failed reading file '%s'\n * %s\n",
                  file_name, strerror(errno) );
        }
      else if (ret==SCANF_FAILURE)
        {
          printf( "Failed closing file '%s'\n * %s\n",
                  file_name, strerror(errno) );
        }
    }

  return 0;
}

scanning data from a new log file

Nothing to do in this exercise, apart from executing the program.


scanning data from a new log file a bit better

Suggested solution:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


#define READ_BUF_SIZE 7

enum
  {
    OK,
    FAILED_OPEN_FILE,
    SCANF_FAILURE,
    CLOSE_FAILURE
  } return_values;


int
read_print_file(char * file_name)
{
  char buf[READ_BUF_SIZE];
  FILE* fp;
  int ret;
  int x;
  int y;
  
  fp = fopen (file_name,"r");
  if (fgfp == NULL)
    {
      return FAILED_OPEN_FILE;
    }

  while (fgets(buf, READ_BUF_SIZE, fp))
    {
      /* printf ("line: '%s'   strlen:%lu \n", */
      /*         buf, strlen(buf)); */
      ret = sscanf(buf, "%d;%d", &x, &y);
      if (ret != 2 )
        {
          fprintf(stderr, "Corrupt line: '%s'\n", buf);
          return SCANF_FAILURE;
        }
      printf ("x:%d  y:%d\n", x, y);
      
    }
  if (fclose (fp))
    {
      return CLOSE_FAILURE;
    }

  return OK;
}

int
main (int argc, char** argv)
{
  char* file_name;
  int ret;
  
  /* Make sure we have enough arguments */
  if (argc<2)
    {
      fprintf(stderr, "Missing file argument\n");
      return 1;
    }

  file_name = argv[1];
  ret = read_print_file(file_name);
  if (ret)
    {
      if (ret==FAILED_OPEN_FILE)
        {
          printf( "Failed reading file '%s'\n * %s\n",
                  file_name, strerror(errno) );
        }
      else if (ret==SCANF_FAILURE)
        {
          printf( "Failed scanning data in file '%s'\n",
                  file_name );
        }
      return ret;
    }
  
  return 0;
}

Signals

Signals is a means to make it possible to communicate between processes.

Description

We will look into how to handle signals such as SIGINT (typically caused by pressing Ctrl-C) or user defined signals like SIGUSR1.

Videos

Signals in C (eng) (pdf) (Same as above - Error handling)

Exercises

Endlessly looping

Write a program writing "." to stdout in a loop that will continue forever. Add a sleep for one tenth of a second in the loop to skip wasting CPU. Example:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int
main(void)
{

  while (1)
    {
      printf (".");
      usleep (100000);
    }

  return 0;
}

Compile and execute. You can terminate the program by pressing "Control" and "c" (ctrl-c).

Note: If your "." printouts are not seen you need to add fflush(stdout); after the printf statement.

Endlessly looping pt II

You should now add a handler to the SIGINT signal. Use the function signallike this:

  signal(SIGINT, sighandler);

This means you want to "register" for the SIGINT signal and when your program gets that signal you want the function sighandler to be invoked. So you need to add a function void sighandler(int sig) { ....... } ; to your program. In this function we want you to print a message (any message!) and then exit with a 0.

Compile and execute. You can send the SIGINT signal to your program by pressing "Control" and "c" (ctrl-c). Compile, execute and press ctrl-c, and make sure your program terminates (with a message).

Solutions

Expand using link to the right to see the full content.

Endlessly looping

Code already presented.

Endlessly looping pt II

Suggested solution

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>


void sighandler(int sig)
{
  printf ("SIGINT (%d) cuaght, will exit the program..... bye\n", sig);
  exit(0);
}

int
main(void)
{

  signal(SIGINT, sighandler);
  
  while (1)
    {
      printf (".");
      fflush(stdout);
      usleep (100000);
    }

  return 0;
}


Chapter Links

Our C FAQ

External links

Error handling (from the C Programming wikibook).

Books this chapter is a part of

Programming with C book

Book TOC | previous chapter | next chapter