Suppose for each line of some text file you want to read the entire line into a buffer and do some processing on it.
A common approach is to choose a fixed maximum length and hope for the best with fgets, but such a program breaks if any line's length is greater than this arbitrary limit.
The program below handles all the edge cases concomitant with fgets:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* argv[0], potentially used in error messages */
const char *progname;
typedef void (*processor)(const char *s);
int for_each_line(const char *path, processor p);
/* simple processor that prints each line to the standard output */
void print(const char *s)
{
  printf("%s\n", s);
}
int main(int argc, char **argv)
{
  progname = argv[0];
  if (argc != 2) {
    fprintf(stderr, "Usage: %s file\n", progname);
    return 1;
  }
  return for_each_line(argv[1], print) ? 0 : 1;
}
int for_each_line(const char *path, processor p)
{
  FILE *f;
  char *buf, *line;
  size_t capacity = 80;  /* reasonable guess at max length */
  size_t remaining = capacity;
  int success = 1;
  f = fopen(path, "r");
  if (!f) {
    fprintf(stderr, "%s: open %s: %s\n",
                    progname, path, strerror(errno));
    return 0;
  }
  line = malloc(capacity);
  if (!line) {
    fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
    fclose(f);
    return 0;
  }
  /*
   * On each iteration, read into buf the rest of a line whose length
   * is at most remaining. We can be certain that we have the whole
   * line only when the string contains '\n', in which case we
   * remove the terminator and call the processor on the entire line.
   *
   * Otherwise, we double line's size and try again.
   *
   * It may seem tempting to also test feof(f) to check whether we
   * have the whole line, but in the unlucky edge case where a file
   * doesn't end with '\n' and its last line is exactly remaining-1
   * in length, feof(f) will not yet be true, hence the possibility
   * of printing the last line outside the loop.
   */
  buf = line;
  line[0] = '\0';
  while (fgets(buf, remaining, f)) {
    char *eol = strchr(buf, '\n');
    if (eol) {
      *eol = '\0';
      p(line);
      buf = line;
      remaining = capacity;
      line[0] = '\0';
    }
    else {
      size_t used = buf + remaining - line;
      line = realloc(line, capacity * 2);
      if (!line) {
        fprintf(stderr, "%s: realloc: %s\n", progname, strerror(errno));
        fclose(f);
        return 0;
      }
      buf = line + used - 1;
      capacity *= 2;
      remaining = capacity - used;
    }
  }
  if (errno) {
    fprintf(stderr, "%s: fgets: %s\n", progname, strerror(errno));
    success = 0;
  }
  else if (line[0]) {
    char *eol = strchr(buf, '\n');
    if (eol)
      *eol = '\0';
    p(line);
  }
  fclose(f);
  free(line);
  return success;
}