DMarlon Studio

Lesson 17 · 17 of 20

Errors: return codes and errno · C

Handle errors in C with your own return codes, errno, strtol and strerror: validating order quantities and reporting a missing file.

C · Errors: return codes and errno in DMarlon Studio: the example's code and its output in the terminal

C has no exceptions: each function reports a failure through what it returns. The clearest approach is an enum of return codes, like Status, with the result left in an output parameter: parse_quantity returns OK or an error, and only when everything is fine does it write the quantity to *out, which is why qty stays at 0 for the invalid inputs in the output. status_text turns each code into a fixed message.

`strtol` turns text into a long and leaves in end a pointer to the first character it could not read. If end == text there was no number at all (abc); if *end is not '\0', something is left over (12kg). It is far safer than atoi, which returns 0 for abc and says nothing.

`errno`, from errno.h, is a global variable where the standard library records the last error. It is only written when something fails, so you set it to 0 before calling strtol and check it straight after: ERANGE means the number does not fit in a long, like 99999999999999999999.

fopen also leaves the reason in errno when it returns NULL, and `strerror(errno)` turns it into text: "No such file or directory". `perror("text")` does the same in one call, but writes to the error stream (stderr).

main ends with EXIT_SUCCESS; a program that fails returns EXIT_FAILURE, which is what the scripts that run it see. In DMarlon Studio the Console target shows the output when you run with F9.

Code · main.c

#include <errno.h>#include <stdio.h>#include <stdlib.h>#include <string.h>// Return codes: the function reports what happened; the result goes in *out.typedef enum { OK, ERR_EMPTY, ERR_NOT_NUMBER, ERR_RANGE } Status;const char *status_text(Status s) {    static const char *texts[] = {"ok", "empty text", "not a whole number", "out of range"};    return texts[s];}Status parse_quantity(const char *text, long *out) {    if (*text == '\0') return ERR_EMPTY;    char *end;    errno = 0;                         // strtol only sets errno when it fails    long value = strtol(text, &end, 10);    if (end == text || *end != '\0') return ERR_NOT_NUMBER;  // "12kg": end points at k    if (errno == ERANGE || value < 1 || value > 500) return ERR_RANGE;    *out = value;    return OK;}int main(void) {    const char *inputs[] = {"25", "", "12kg", "abc", "99999999999999999999", "0"};    for (int i = 0; i < 6; i++) {        long qty = 0;        Status st = parse_quantity(inputs[i], &qty);        printf("%-22s %s (%ld)\n", *inputs[i] ? inputs[i] : "(empty)", status_text(st), qty);    }    FILE *f = fopen("missing-orders.csv", "r");  // the standard library uses errno    if (f == NULL) printf("Cannot open missing-orders.csv: %s\n", strerror(errno));    else fclose(f);    return EXIT_SUCCESS;}

Output · cc main.c -o main && ./main

25                     ok (25)
(empty)                empty text (0)
12kg                   not a whole number (0)
abc                    not a whole number (0)
99999999999999999999   out of range (0)
0                      out of range (0)
Cannot open missing-orders.csv: No such file or directory

Key points

  • Return a status code and leave the result behind a pointer.
  • strtol with end catches empty or leftover text.
  • Set errno = 0 before the call and check it right after.
  • strerror(errno) and perror explain the system error.

Exercise

Add "-3" to the inputs and an ERR_NEGATIVE code so that it gives "negative quantity" instead of "out of range", and make main return EXIT_FAILURE if any input is invalid.