sentinel return values
/ sentinel: "SEN-tih-nuhl" /
Imagine a thermometer where a reading of exactly -999 does not mean a temperature - it means 'sensor broken, ignore me'. That special, impossible-as-a-real-answer value is a sentinel: a particular return value reserved to mean 'failure' rather than a normal result. C functions use sentinels constantly to pack 'did it work?' and 'here is the answer' into a single returned number.
The idea is to pick a value that could never be a legitimate success and reserve it for failure. The standard choices follow a few patterns: functions returning a pointer use NULL for failure (malloc(), fopen(), strchr() when not found); functions returning a count or descriptor use -1 for failure (open(), read(), write() - which return non-negative on success, so -1 is safely out of range); functions returning a small status use 0 for success and non-zero for failure (the reverse of booleans, which trips up beginners). The caller checks against the sentinel: if (p == NULL), if (fd == -1), if (rc != 0). This is the success/failure return idiom - one value does double duty, signalling outcome and carrying data.
Why it matters and where it bites: a sentinel is cheap and ubiquitous, but it has a real flaw - it shrinks the set of valid answers. The classic trap is a function whose entire output range is meaningful, leaving no spare value for a sentinel. The textbook case is getchar(): it returns characters, but to also signal end-of-file it must return EOF, which is why getchar() returns int, not char - it needs one extra value beyond every possible byte. Another trap is overloading: if -1 is both a sentinel and a possible real result, you cannot tell them apart from the return value alone, which is exactly the situation that forces the return-value-plus-errno split. Richer languages solve this with a separate type (Option, Result) so the success and failure cases can never be confused with each other.
int c; while ((c = getchar()) != EOF) { putchar(c); } - getchar() returns each byte as a non-negative int, and the sentinel EOF (a negative value) for end-of-file. Storing it in char c instead of int c would be a bug: a byte equal to 0xFF could be mistaken for EOF, or EOF could never be detected.
EOF is a sentinel that lives outside the range of any real byte, which is why getchar() returns int.
A sentinel works only when the failure value can never be a valid result. When every output is meaningful, you need a wider type (like int for getchar) or a separate channel (return value plus errno), or you risk confusing a real answer with 'failed'.