- Richard Feynman -
Chapter:Error handling in C
Contents
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:
- C Error handling (eng)
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
- Error handling in C - pt I (eng) (pdf)
- Error handling in C - pt II (eng) (same pdf as above)
- Error handling in C - pt III (eng) (same pdf as above)
- Error handling in C - gets (don't use gets!) (eng) (live coding video) gets-example.c
- Error handling in C - atoi (don't use atoi!) (eng) (live coding video) atoi-example.c
- Error handling in C - using scanf properly (eng) (live coding video) scanf-example.c
- Error handling in C - printf and return values (eng) (live coding video) printf-example.c
- Error handling in C - printing the content of a file (poorly) (eng) (live coding video) print-textfile-bad.c
- 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 signal
like 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).