C Part I · Module 12 · Standard Library, Files and Program Interfaces
How to Read the C Standard Library
You have been using this library since Chapter 1.4 and have met four of its functions. It has several hundred, and no useful chapter could list them. What a chapter can do — and what makes the rest of this module possible — is give you a way to read one you have never seen: four questions that pull a function's contract out of any reference page in about a minute, and that keep working for functions this curriculum will never mention. The questions matter more than any particular answer, because the library's failure conventions are not consistent. Four different ones are in use, they are not interchangeable, and the most dangerous of them returns a value that looks entirely legitimate while something has gone wrong.
Intermediate18 min readCPart IFundamentalsStandard Library
Part I · Module 12 · Chapter 12.1 · How to Read the C Standard Library
1. The Library Is Part of the Language
The C standard specifies two things: the language and the library. They are in one document, written to the same level of precision, and a conforming implementation must provide both. printf is not an add-on that happens to ship with your compiler; it is as much a part of C as while.
That has a practical consequence worth stating before anything else: the library's guarantees are of the same kind as the language's. When Module 9 said that reading freed memory is undefined, and Module 10 that padding bytes are unspecified, those were statements from the same document that says what fopen returns on failure. You already know how to read that kind of statement — which is most of this chapter.
C17 specifies 29 headers. On the machine used for these measurements, 27 of them could be included:
includable: 27 of 29
not available: threads.h uchar.h§6 is about why, and about the difference between the two missing ones.
2. The Four Questions
For any library function, ask:
1. What does it assume? The preconditions you must satisfy. Violate one and the behaviour is undefined — the function will not check.
2. What does it return? The type, and what the value means when everything worked.
3. How does it report failure? Which of §3's four conventions, and the exact value.
4. What does it not promise? The parts deliberately left unspecified or implementation-defined.
Question 4 is the one people skip and the one that produces portable-looking code that is not. Chapter 9.6 §6 is a worked example: malloc(0) is implementation-defined, so a program that relies on either answer is relying on something the contract does not give.
Applied to a function you already know:
| Question | malloc |
|---|---|
| assumes | nothing about the argument; size may be any size_t |
| returns | a void * to a block of at least size bytes, suitably aligned, with indeterminate contents |
| fails by | returning NULL — convention A |
| does not promise | that the contents are zero; what malloc(0) returns; that the block is contiguous with anything |
Four lines, and every bug in Module 9 is a violation of one of them.
3. The Four Failure Conventions
This is where a method beats memorisation, because the conventions are not consistent across the library and using the wrong one produces code that looks correct.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
int main(void)
{
FILE *f = fopen("/definitely/not/here", "r"); /* A */
printf("fopen on a missing path -> %s\n", f == NULL ? "NULL" : "non-NULL");
void *p = malloc(SIZE_MAX); /* A */
printf("malloc(SIZE_MAX) -> %s\n", p == NULL ? "NULL" : "non-NULL");
free(p);
errno = 0; /* C: clear first */
char *end;
long v = strtol("99999999999999999999", &end, 10);
printf("strtol overflow -> %ld, errno==ERANGE ? %d\n", v, errno == ERANGE);
printf("strlen(\"abc\") -> %zu\n", strlen("abc")); /* D */
return 0;
}fopen on a missing path -> NULL
malloc(SIZE_MAX) -> NULL
strtol overflow -> 9223372036854775807, errno==ERANGE ? 1
strlen("abc") -> 3Convention A — a sentinel return. A reserved value that cannot be a success. fopen and malloc return NULL; fgets returns NULL; ftell returns -1L. These are the easy ones, and Chapter 9.6 §3's rule covers them: check at the boundary, once.
Convention B — a short count. The function returns how many items it handled, which may be fewer than you asked for.
fread asked 64, got 3 feof=1 ferror=0The count alone does not say why. Three of sixty-four might mean the file ended or might mean an error occurred, and fread's contract says you must ask feof or ferror to find out. A program that treats a short count as "end of input" silently swallows read errors.
Convention C — a value plus errno. The return value is in range and looks completely legitimate.
strtol on an overflowing input returned 9223372036854775807 — LONG_MAX. There is nothing wrong with that number. The only thing distinguishing it from a successful conversion of a very large value is errno, which is ERANGE. This convention is the dangerous one precisely because there is no visual cue.
Convention D — it cannot fail. strlen has no failure value; neither does memcpy or abs. That does not make them safe — it moves the entire burden to question 1, because the only way to get a wrong answer from strlen is to violate its precondition, and then the behaviour is undefined rather than wrong.
4. The One errno Fact You Need Now
Convention C is unusable without this, and it catches people out constantly:
errno = 0;
FILE *bad = fopen("/no/such/path", "r");
printf("after a failed fopen : errno=%d (%s)\n", errno, strerror(errno));
FILE *good = fopen("tiny.txt", "r");
printf("after a SUCCESSFUL fopen, errno is still %d (%s)\n", errno, strerror(errno));after a failed fopen : errno=2 (No such file or directory)
after a SUCCESSFUL fopen, errno is still 2 (No such file or directory)A successful call does not clear errno. The library sets it on failure and is permitted to set it on success too; nothing ever resets it for you.
errnois only meaningful after a call that has already reported failure by its own convention. Testingerrnoto decide whether something failed reads a value that may be left over from any earlier call — possibly one inside the library that you never made.
So convention C always has two steps: set errno = 0 immediately before the call, then test the documented failure indication and errno afterwards. Chapter 12.8 is the full treatment; this is the part you need to read the other eight chapters.
5. What a Contract Does Not Say
Question 1 produces the preconditions, and a precondition is not a check the function performs.
size_t strlen(const char *s);Its precondition is that s points to a string — a sequence of characters with a terminating null, entirely within one object. If you hand it something else, strlen does not return an error. It walks memory until it finds a zero byte, which is Chapter 8.5's measured failure mode.
A library function assumes its preconditions and checks almost nothing. The contract tells you what you must guarantee; guaranteeing it is your side of the bargain, and the consequence of failing is undefined behaviour, not a diagnostic.
Four preconditions worth recognising because they recur across the whole library:
A pointer must be valid and non-null unless the contract explicitly permits null. free(NULL) is defined (Chapter 9.6 §5); strlen(NULL) is not.
A buffer must be large enough for everything the function may write. Chapter 8.5's entire subject.
Source and destination must not overlap, for many functions — memcpy says so explicitly, memmove is the one that permits it.
A string must be terminated. Chapter 8.1's rule, and the precondition behind most of <string.h>.
When a contract and your code disagree, the contract wins silently.
6. Where to Find the Answers
In decreasing order of authority:
The standard itself. The final word on what is guaranteed, implementation-defined, unspecified or undefined. Working drafts are freely available and are the place to settle an argument.
Your platform's manual pages — man 3 fopen. Fast, usually accurate, and they describe your implementation, which means they mix standard behaviour with platform extensions without always marking which is which. Excellent for looking something up, risky for deciding what is portable.
A good online reference. The best ones mark which standard version introduced each function and flag the implementation-defined parts.
The header on your system. It gives you the exact prototype and nothing about behaviour. Useful for settling a signature question, useless for a contract.
Use the manual for speed and the standard for portability questions. If you are asking "will this work on another compiler", only one of the four sources can answer.
7. Headers, and the Optional Parts
C17's 29 headers divide into groups that are worth knowing as groups rather than as a list:
| Group | Headers | For |
|---|---|---|
| Essential | stdio.h stdlib.h string.h | I/O, allocation and conversion, strings |
| Types and limits | stddef.h stdint.h limits.h float.h stdbool.h inttypes.h | sizes, widths, ranges |
| Diagnostics | assert.h errno.h | checking and error reporting |
| Numeric | math.h complex.h tgmath.h fenv.h | arithmetic beyond the operators |
| Characters and text | ctype.h wchar.h wctype.h uchar.h locale.h | classification and wide characters |
| Control and time | signal.h setjmp.h time.h | program control, clocks |
| Concurrency | threads.h stdatomic.h | threads and atomics |
| Variadic and alignment | stdarg.h stdalign.h stdnoreturn.h iso646.h | function and declaration support |
Some of it is optional, and the standard tells you how to ask — but only for some parts. Measured here:
__STDC_NO_THREADS__ defined -> <threads.h> is NOT provided
__STDC_NO_ATOMICS__ not defined -> atomics provided
__STDC_NO_COMPLEX__ not defined -> complex provided
__STDC_NO_VLA__ not defined -> VLAs providedincludable: 27 of 29
not available: threads.h uchar.h8. Applying the Method
Three functions, read with the four questions. None of them is taught here; the point is that the method produces a usable answer without the chapter that covers them.
fgets
| assumes | buf points to at least n writable bytes; n > 0; stream is open for reading |
| returns | buf on success |
| fails by | returning NULL — convention A — for end-of-file or error, so feof/ferror decides which |
| does not promise | that a full line was read; that the result contains a newline |
That last row is the one that produces bugs, and Chapter 12.3 is about it.
strtol
| assumes | the string is null-terminated; base is 0 or 2–36 |
| returns | the converted value; *end set to the first unconverted character |
| fails by | convention C — LONG_MAX/LONG_MIN with errno == ERANGE; and "no digits" is reported by *end == input |
| does not promise | that it converted anything at all |
Two failure modes, reported two different ways, neither of them by the return value alone.
memcpy
| assumes | both pointers valid for n bytes; the regions do not overlap |
| returns | its destination argument |
| fails by | convention D — it cannot |
| does not promise | anything about overlapping regions, where behaviour is undefined |
The entire risk is in row one, which is why Chapter 12.9 pairs it with memmove.
9. What You Should Be Able to Say Now
- The library is specified by the same standard as the language, to the same precision.
- C17 has 29 headers; measured here, 27 were includable.
- The four questions: what does it assume, what does it return, how does it report failure, what does it not promise.
- Question 4 is the one people skip and the one that produces non-portable code.
- Four failure conventions, not interchangeable: sentinel, short count, value plus
errno, cannot fail. - Measured:
fopen→ NULL;freadasked 64 and got 3 withfeof=1;strtoloverflow → LONG_MAX witherrno == ERANGE;strlen("abc")→ 3. - A short count does not say why — ask
feoforferror. - Convention C's failure value looks legitimate. There is no visual cue.
- Measured: a successful
fopenlefterrnoat 2. Seterrno = 0before, and test it only after a documented failure. - A precondition is not a check. The library assumes and almost never verifies.
- The standard for portability, the manual for speed.
- Only threads, atomics, complex and VLAs have conditional-feature macros —
uchar.hwas simply absent, with nothing to announce it.
10. Exercises
Exercise 1 — answer the four questions. For each, using any reference you like, write the four-row table from §2.
fputscallocatoimemmove
Exercise 2 — name the convention. For each, say which of §3's four it uses and what the exact failure indication is.
FILE *fopen(const char *, const char *);
size_t fwrite(const void *, size_t, size_t, FILE *);
int fclose(FILE *);
double strtod(const char *, char **);
void *memset(void *, int, size_t);
int printf(const char *, ...);Exercise 3 — the legitimate-looking failure. Explain why strtol returning LONG_MAX is harder to handle than fopen returning NULL, and write the complete correct call including both failure modes from §8.
Exercise 4 — the short count. A loop reads with fread and stops when the count is less than requested, treating that as end of input. Describe the bug, name the two functions that distinguish the cases, and rewrite the stopping condition.
Exercise 5 — the stale errno. This code reports a failure that did not happen. Say why, and give the two-line fix.
FILE *f = fopen(path, "r");
if (errno != 0) { report("could not open"); return -1; }Exercise 6 — find the precondition. For each call, state the precondition being violated and what the contract says happens.
strlen(NULL)
memcpy(buf, buf + 2, 10)
strcpy(dst, src) /* dst is 8 bytes, src is "a longer string" */
fgets(buf, 0, stdin)Exercise 7 — portability question. You must decide whether a function you have never used is safe to call on three different compilers. Say which of §6's four sources you would consult, in what order, and what each one can and cannot settle.
Exercise 8 — test for the optional parts. Write the preprocessor code that reports which of threads, atomics and complex arithmetic your implementation provides. Then say why the same technique cannot tell you whether <uchar.h> exists.
11. Summary
- The library is part of the language, specified to the same precision. 29 headers; 27 includable here.
- Four questions: assumes · returns · reports failure · does not promise.
- Four failure conventions: sentinel · short count · value plus
errno· cannot fail. - Measured:
fopen→ NULL ·fread3 of 64 withfeof=1·strtol→ LONG_MAX,errno == ERANGE·strlen→ 3. - A short count does not say why; convention C's failure looks legitimate.
- Measured: a successful call left
errnoset. Clear it before; test it only after a reported failure. - A precondition is not a check — the library assumes and does not verify.
- The standard settles portability; the manual is for speed.
- Only four features have conditional macros — a missing header may be announced by nothing at all.
The next chapter takes the function you have used more than any other and reads it with exactly this method, which turns out to be more interesting than it sounds. printf is variadic, so the compiler cannot check its arguments against its format string by any ordinary rule — and the standard's position on a mismatch is not that you get odd-looking output. It is that the behaviour is undefined, in the full Chapter 9.12 sense, which means the question is not what gets printed but whether the program has any meaning at all. There is also a warning flag that recovers most of the checking, which is on by default in one of the two configurations this curriculum uses and which you should make sure is on in yours.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
