This article looks at handling errors in C APIs using an if-error-goto coding pattern which is an improvement over the if-error-else variant.

Introduction

If opening a file fails (e.g. because of the file permissions) fopen returns a null pointer. The code has to test for this case. If an error happened, after logging the error goto is used to jump to the section at the end of the function where cleanup is performed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FILE * f = 0;

// after all inits:
f = fopen("file.name", "rb");
if ( ! f)
{
  perror("Failed to open file.name");
  goto end_label;
}

// more
// code
// here

end_label:

if (f)
{
  fclose(f);
}

I refer to this coding style the if-error-goto coding pattern.

Compared with the if-error-else style, it addresses the nesting problem: the sample code below goes to 3 levels deep down from 6 levels.

It also addresses the irregular error handling. Inside the for loop one would still use goto in case of an error (instead of choosing to break from the loop).

Issues

This style still has the issue of too much repetition, and the same comments from the if-error-else style apply here as well. Every time we invoke fopen using this coding pattern we need to repeat:

  • the if condition
  • the error handling
  • the goto
  • the if condition for fclose
  • the fclose call
  • and 4 curly brackets

For the copy file example this approach takes the code from 28 lines of code to 78 lines of code, almost triple.

The code repetition can be addressed using a C++ RAII approach.

The other objectionable issue is the usage of goto. It is a bit antiquated, but not really harmful, because it’s regular, not jumping all over the place.

Full code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
#include <stdlib.h>

int main ()
{
  int return_value = 1;
  FILE * src = 0;
  FILE * dst = 0;
  char * buffer = 0;

  src = fopen("src.bin", "rb");
  if ( ! src)
  {
    perror("Failed to open source file");
    goto end;
  }

  dst = fopen("dst.bin", "wb");
  if ( ! dst)
  {
    perror("Failed to open destination file");
    goto end;
  }

  const size_t buffer_size = 1024;
  buffer = malloc(buffer_size);
  if ( ! buffer)
  {
    fputs("Failed to allocate buffer\n", stderr);
    goto end;
  }

  for(;;)
  {
    size_t read_count = fread(buffer, 1, buffer_size, src);
    if ((read_count != buffer_size) && ferror(src))
    {
      perror("Failed to read from source file");
      goto end;
    }

    if (read_count > 0)
    {
      size_t write_count = fwrite(buffer, 1, read_count, dst);
      if (write_count != read_count)
      {
        perror("Failed to write to destination file");
        goto end;
      }
      fputs(".", stdout);
    }

    if (feof(src))
    {
      return_value = 0;
      fputs("\nSUCCESS\n", stdout);
      break;
    }
  }

end:
  if (buffer)
  {
    free(buffer);
  }

  if (dst)
  {
    fclose(dst);
  }

  if (src)
  {
    fclose(src);
  }

  return return_value;
}

Summary

While an improvement over the if-error-else approach, the if-error-goto is still verbose, repetitive and error prone. Don’t use it, unless for whatever reason you can’t use C++ and RAII.