Skip to content

C standard library cheatsheet from first input and output to memory safety, parsing, files, callbacks, concurrency, portability, and production patterns.

libc is the library interface that makes ordinary C programs useful: formatted I/O, memory allocation, strings, numeric conversion, files, math, time, error handling, sorting, process termination, and more.

your program -> ISO C library API -> libc implementation -> operating system/runtime
<stdio.h>, <stdlib.h>, ...

This page uses ISO C17 as its portable baseline. Functions marked C11 require C11 support; names marked POSIX are common on Unix-like systems but are not part of ISO C. C23 and implementation-specific additions are noted only where useful.

NameMeaning
ISO C standard libraryThe portable interfaces declared by standard C headers
libcAn implementation of much of that library plus platform extensions
glibcCommon GNU/Linux libc implementation
muslSmall Linux libc implementation often used in static containers
MSVC Universal CRTThe Microsoft C runtime library on modern Windows
POSIX APIAdditional Unix-like interfaces such as getline, strdup, and localtime_r

In C++, headers such as <cstdio> and <cstdlib> expose corresponding C facilities in the C++ standard library. This page is about writing C.


hello.c
#include <stdio.h>
int main(void) {
puts("Hello, libc!");
return 0;
}
Terminal window
cc -std=c17 -Wall -Wextra -Wpedantic hello.c -o hello
./hello

puts writes text followed by a newline to standard output.

Every hosted C program begins with three text streams already available:

StreamPurposeCommon use
stdinStandard inputKeyboard input or piped data
stdoutStandard outputNormal results
stderrStandard errorDiagnostics and errors
#include <stdio.h>
int main(void) {
fprintf(stdout, "normal result\n");
fprintf(stderr, "diagnostic message\n");
return 0;
}

Keep diagnostics on stderr so users can redirect program output separately:

Terminal window
./app >results.txt 2>errors.txt

Do not rely on implicit declarations or transitive includes. Include the header that declares the function, type, or macro you use:

#include <stdio.h> // printf, FILE
#include <stdlib.h> // malloc, strtol, EXIT_SUCCESS
#include <string.h> // strlen, memcpy

HeaderMain purposeRepresentative names
<stdio.h>Streams and formatted I/Oprintf, fgets, fopen, fread
<stdlib.h>Allocation, conversion, algorithms, terminationmalloc, strtol, qsort, exit
<string.h>Byte strings and raw memorystrlen, strcmp, memcpy, memmove
<ctype.h>Byte character classification/conversionisdigit, isspace, tolower
<errno.h>Error indicator macroerrno, ERANGE
<assert.h>Debug assertionsassert
<limits.h>Integer limitsINT_MAX, CHAR_BIT
<stdint.h>Exact/least/fast width integersuint32_t, INT64_MAX
<inttypes.h>Portable integer formattingPRIu64, SCNd64
<stddef.h>Core types and macrossize_t, ptrdiff_t, NULL, offsetof
<stdbool.h>Boolean spelling through C17bool, true, false
HeaderPurposeRepresentative names
<math.h>Floating-point mathematicssqrt, sin, isfinite
<float.h>Floating-point limitsDBL_EPSILON, DBL_MAX
<time.h>Calendar and processor timetime, strftime, clock
<locale.h>Locale selection/queryingsetlocale, localeconv
<signal.h>Signalssignal, raise, SIGINT
<setjmp.h>Non-local jumpssetjmp, longjmp
<stdarg.h>Variadic argument accessva_list, va_start
<wchar.h>Wide strings and wide I/Owchar_t, wprintf
<wctype.h>Wide character classificationiswalpha, towupper
<complex.h>Complex arithmeticdouble complex, cabs
<fenv.h>Floating-point environmentfeclearexcept, fetestexcept
<tgmath.h>Type-generic math macrossqrt-style dispatch
<stdatomic.h>Atomics, C11atomic_int, atomic_fetch_add
<threads.h>Threads, C11 optional in implementationsthrd_create, mtx_lock
<uchar.h>UTF-related character conversion, C11char16_t, mbrtoc16
<stdalign.h>Alignment convenience macros, C11alignas, alignof
<stdnoreturn.h>Non-returning function macro, C11noreturn
<iso646.h>Alternative operator macro spellingsand, or, not

A hosted implementation provides the complete standard-library environment expected by desktop/server applications. A freestanding implementation, often used for kernels or embedded firmware, only guarantees a smaller language/library subset. Check your platform before assuming files, allocation, signals, or a console exist.


FunctionDestinationNotes
printf(format, ...)stdoutFormatted terminal/redirected output
fprintf(stream, format, ...)A FILE *Use for files or stderr
snprintf(buf, size, format, ...)Character arrayBounded formatted output
sprintf(buf, format, ...)Character arrayAvoid: has no buffer-size argument
#include <stdio.h>
int main(void) {
const char *name = "Mira";
int score = 42;
double ratio = 0.875;
printf("%s scored %d (%.1f%%)\n", name, score, ratio * 100.0);
fprintf(stderr, "loaded user: %s\n", name);
char line[64];
int needed = snprintf(line, sizeof line, "%s:%d", name, score);
if (needed < 0) {
fputs("formatting failed\n", stderr);
return 1;
}
if ((size_t)needed >= sizeof line) {
fputs("formatted line was truncated\n", stderr);
return 1;
}
puts(line);
}

snprintf returns the number of characters that would have been written, excluding the terminating null character. A nonnegative result at least as large as the capacity means truncation.

Value typeSpecifierExample
int%d or %iprintf("%d", n);
unsigned int%uprintf("%u", n);
long / unsigned long%ld / %luprintf("%ld", n);
long long / unsigned long long%lld / %lluprintf("%lld", n);
Hexadecimal unsigned%x, %Xprintf("%08x", bits);
double%f, %e, %gprintf("%.3f", value);
long double%Lf, %Le, %Lgprintf("%Lg", value);
Character%cprintf("%c", ch);
Null-terminated string%sprintf("%s", text);
Pointer converted to void *%pprintf("%p", (void *)ptr);
Literal percent sign%%printf("90%%");
size_t%zuprintf("%zu", count);
ptrdiff_t%tdprintf("%td", distance);

For fixed-width integer types, use <inttypes.h> macros:

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
uint64_t bytes = UINT64_C(9000000000);
printf("bytes=%" PRIu64 "\n", bytes);
printf("|%8d|\n", 17); // Right aligned: | 17|
printf("|%-8d|\n", 17); // Left aligned: |17 |
printf("|%08x|\n", 0x2a); // Zero padded: |0000002a|
printf("%.2f\n", 3.14159); // 3.14
printf("%.5s\n", "library"); // libra

Never let untrusted data become the format string:

printf("%s", user_text); // Correct
// printf(user_text); // Format-string vulnerability

For interactive or text input, read a line first, then parse it. This gives you control over malformed input and leftover characters.

#include <stdio.h>
int main(void) {
char line[128];
fputs("Name: ", stdout);
fflush(stdout);
if (fgets(line, sizeof line, stdin) == NULL) {
if (ferror(stdin)) {
perror("stdin");
}
return 1;
}
printf("You entered: %s", line);
return 0;
}

fgets stores the newline when it fits. Remove one trailing newline when needed:

#include <string.h>
line[strcspn(line, "\n")] = '\0';

If a line is longer than the buffer, fgets returns a partial line. A robust program detects the missing newline and consumes or handles the remainder.

atoi cannot distinguish invalid input from a valid zero and gives poor range handling. Prefer strtol, strtoul, strtoll, or strtoull.

#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int parse_int(const char *text, int *out) {
char *end;
long value;
errno = 0;
value = strtol(text, &end, 10);
if (end == text) {
return 0; // No digits
}
if (errno == ERANGE || value < INT_MIN || value > INT_MAX) {
return 0; // Out of int range
}
while (*end == ' ' || *end == '\t' || *end == '\n') {
++end;
}
if (*end != '\0') {
return 0; // Unexpected suffix
}
*out = (int)value;
return 1;
}
FunctionParsesUseful base behavior
strtol, strtollSigned integerBase 10 for decimal; base 0 recognizes common prefixes
strtoul, strtoullUnsigned integerValidate whether negative spelling should be rejected
strtof, strtod, strtoldFloating pointCheck end and ERANGE

Formatted scanning is useful for controlled formats, but checking its return count is mandatory:

int x;
double y;
if (sscanf("12 3.5", "%d %lf", &x, &y) == 2) {
printf("%d %.1f\n", x, y);
}
Scan typeSpecifier
int *%d
unsigned int *%u
long *%ld
long long *%lld
float *%f
double *%lf
long double *%Lf
char * single character%c

Limit string input width so room remains for '\0':

char word[32];
if (scanf("%31s", word) == 1) {
puts(word);
}

For user-entered lines with spaces or recovery from errors, fgets plus conversion functions is generally easier to make reliable than scanf.


A C string is an array of char terminated by a null character:

char mutable_text[] = "cat"; // {'c', 'a', 't', '\0'}, writable array
const char *literal = "cat"; // Do not modify string-literal storage
FunctionMeaningImportant condition
strlen(s)Number of characters before '\0's must be terminated
strcmp(a, b)Lexicographic comparisonResult is negative, zero, or positive
strncmp(a, b, n)Compare up to n charactersNot a general constant-time comparison
strchr(s, c)Find first characterReturns pointer or NULL
strrchr(s, c)Find last characterReturns pointer or NULL
strstr(s, needle)Find substringReturns pointer or NULL
strspn(s, accept)Initial accepted run lengthUseful for token validation
strcspn(s, reject)Initial run before rejected charUseful for stripping newline
strpbrk(s, chars)Find any listed charReturns pointer or NULL

The destination must always be large enough for copied text and its terminating null character.

#include <stdio.h>
#include <string.h>
int make_label(char *dst, size_t cap, const char *name, unsigned id) {
int n = snprintf(dst, cap, "%s-%u", name, id);
return n >= 0 && (size_t)n < cap;
}
FunctionTrap
strcpy, strcatCannot know destination capacity; safe only after a proven size check
strncpyMay leave destination unterminated and pads remaining space; it is not a simple safe strcpy
strncatThird argument counts appended characters, not destination capacity

For constructing text, a checked snprintf is often clearer than a chain of copies and appends.

Unlike string operations, memory operations work with an explicit number of bytes and do not care about '\0'.

FunctionPurposeRule
memcpy(dst, src, n)Copy n bytesSource and destination must not overlap
memmove(dst, src, n)Copy n bytesHandles overlap
memset(dst, byte, n)Set each byteA byte fill, not general typed initialization
memcmp(a, b, n)Compare bytesUseful for byte representations/data, not padded structs in general
memchr(data, byte, n)Find a byteWorks on non-string data
int values[] = {10, 20, 30, 40};
memmove(&values[1], &values[0], 3 * sizeof values[0]);
// values is now {10, 10, 20, 30}

Do not use memcpy for overlapping portions of one array; overlapping arguments invoke undefined behavior.

strtok modifies its input string and keeps hidden iteration state:

#include <stdio.h>
#include <string.h>
char csv[] = "red,green,blue";
for (char *part = strtok(csv, ","); part != NULL; part = strtok(NULL, ",")) {
puts(part);
}

This is acceptable for simple one-at-a-time parsing. It does not preserve empty fields and is awkward for nested or concurrent parsing. POSIX strtok_r and Microsoft’s strtok_s are platform-specific alternatives with different portability considerations.


<ctype.h> classifies or maps single-byte character values in the current locale.

FunctionTest or conversion
isalpha(c)Letter
isdigit(c)Decimal digit
isalnum(c)Letter or digit
isspace(c)Whitespace
isxdigit(c)Hexadecimal digit
isprint(c)Printable including space
tolower(c)Lowercase mapping if available
toupper(c)Uppercase mapping if available

The argument must be EOF or representable as unsigned char. Cast potentially signed char data:

#include <ctype.h>
for (char *p = text; *p != '\0'; ++p) {
*p = (char)tolower((unsigned char)*p);
}

ctype is not a UTF-8 string-processing library. UTF-8 characters can occupy multiple bytes.


FunctionMeaning
malloc(bytes)Allocate uninitialized storage
calloc(count, size)Allocate storage whose bytes are initialized to zero
realloc(ptr, bytes)Resize allocation, possibly moving it
free(ptr)Release allocation; free(NULL) is allowed
aligned_alloc(alignment, size)C11 aligned allocation; size requirements apply

In C, do not cast malloc’s void * result; including <stdlib.h> is what provides the correct declaration.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 100;
if (count > SIZE_MAX / sizeof(int)) {
fputs("requested array is too large\n", stderr);
return EXIT_FAILURE;
}
int *values = calloc(count, sizeof *values);
if (values == NULL) {
perror("calloc");
return EXIT_FAILURE;
}
values[0] = 7;
free(values);
values = NULL;
return EXIT_SUCCESS;
}

Include <stdint.h> for SIZE_MAX where it is provided, or use count > (size_t)-1 / sizeof *values when writing to environments with different header availability.

If realloc fails for a nonzero request, the original allocation remains valid. Do not overwrite its only pointer until success:

if (count > SIZE_MAX / 2) {
free(values);
return EXIT_FAILURE;
}
size_t new_count = count * 2;
if (new_count > SIZE_MAX / sizeof *values) {
free(values);
return EXIT_FAILURE;
}
int *grown = realloc(values, new_count * sizeof *values);
if (grown == NULL) {
free(values);
return EXIT_FAILURE;
}
values = grown;
count = new_count;

Avoid using realloc(ptr, 0) as a portable free idiom; zero-size behavior has varied across standards and implementations. Write free(ptr) explicitly.

RuleWhy it matters
Decide who owns each allocationPrevents leaks and double frees
Free exactly once after the last usePrevents dangling-pointer access
Never free stack storage or string literalsOnly allocated blocks may be freed
Store allocation counts as size_tMatches library sizes and sizeof
Check count * sizeof element for overflowPrevents undersized allocations
Do not read uninitialized malloc memoryIts values are indeterminate
BugExample symptomDetection tool
Out-of-bounds writeCrash later or corrupt outputAddressSanitizer
Use after freeIntermittent failuresAddressSanitizer
LeakGrowing memory consumptionLeakSanitizer or Valgrind
Double freeAllocator abort/crashAddressSanitizer
Integer overflow in allocation sizeSmall allocation followed by overflowWarnings, reviews, sanitizers
Terminal window
cc -std=c17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined -g app.c -o app
./app

Sanitizer availability and flag spelling depend on the compiler and platform.


#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("notes.txt", "r");
if (fp == NULL) {
perror("notes.txt");
return EXIT_FAILURE;
}
char line[256];
while (fgets(line, sizeof line, fp) != NULL) {
fputs(line, stdout);
}
if (ferror(fp)) {
perror("reading notes.txt");
fclose(fp);
return EXIT_FAILURE;
}
if (fclose(fp) == EOF) {
perror("closing notes.txt");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
ModeMeaningExisting contents
"r"Read textFile must exist
"w"Write textTruncated or created
"a"Append textCreated if absent; writes go at end
"r+"Read/write textFile must exist
"w+"Read/write textTruncated or created
"a+"Read/append textCreated if absent
"rb", "wb", etc.Binary equivalentsImportant on systems distinguishing text and binary
TaskReadingWriting
One characterfgetcfputc
Text line/stringfgetsfputs
Formatted valuesfscanffprintf
Blocks/recordsfreadfwrite

Do not write while (!feof(fp)). EOF is set only after an attempted read fails. Loop on the read operation:

unsigned char buf[4096];
size_t n;
while ((n = fread(buf, 1, sizeof buf, fp)) != 0) {
if (fwrite(buf, 1, n, out) != n) {
perror("write");
/* handle error */
}
}
if (ferror(fp)) {
perror("read");
}
FunctionPurpose
fseek(fp, offset, origin)Seek in a stream where supported
ftell(fp)Obtain current position for matching use with fseek
rewind(fp)Return to start and clear status indicators
fgetpos, fsetposPortable stream position save/restore
fflush(fp)Push pending output for an output/update stream
setvbuf(fp, buffer, mode, size)Configure buffering before I/O
feof(fp)Test EOF indicator after a read failed
ferror(fp)Test error indicator
clearerr(fp)Clear EOF and error indicators

fflush(stdin) is not a portable way to discard unread input; its standard use is for output streams and certain update-stream transitions.

FunctionPurposeNote
remove(path)Remove a fileExact filesystem behavior is platform-dependent
rename(old, new)Rename a fileReplacement behavior may vary
tmpfile()Create an automatically removed temporary binary fileCheck for NULL
tmpnam()Generate a temporary nameAvoid for secure programs because of race risk

errno is meaningful only when a function documents that it reports errors through it and its return value indicates failure. A successful call need not clear old errno values.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
FILE *fp = fopen("settings.txt", "r");
if (fp == NULL) {
int saved = errno;
errno = saved;
perror("settings.txt");
}
FacilityUse
errnoInteger-like macro holding an error code for the current execution context
perror(prefix)Print prefix plus message corresponding to current errno
strerror(error_number)Obtain error-message text
EDOMDomain error reported by some math functions
ERANGERange error from conversions or math functions

Use assert to reveal programmer mistakes and broken internal assumptions, not expected bad user input:

#include <assert.h>
double average(const int *values, size_t count) {
assert(values != NULL);
assert(count != 0);
/* compute result */
return 0.0;
}

Compiling with -DNDEBUG disables assert evaluations. Never put required side effects inside an assertion.

Function or macroBehavior
return EXIT_SUCCESS; from mainNormal successful termination
return EXIT_FAILURE; from mainPortable indication of failure
exit(status)Normal termination; flushes/closes standard streams and runs atexit callbacks
_Exit(status)Immediate C99 termination without normal cleanup
abort()Abnormal termination, generally signaling a fatal internal condition
atexit(callback)Register normal-exit callbacks in reverse registration order
#include <stdio.h>
#include <stdlib.h>
static void report_shutdown(void) {
fputs("shutdown complete\n", stderr);
}
int main(void) {
if (atexit(report_shutdown) != 0) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

10. Numeric Conversion, Randomness, and Algorithms

Section titled “10. Numeric Conversion, Randomness, and Algorithms”
FunctionConverts
strtol, strtollText to signed integer
strtoul, strtoullText to unsigned integer
strtof, strtod, strtoldText to floating point
atoi, atol, atoll, atofSimpler conversion without useful error reporting; avoid for validated input

rand is suitable for simple simulation examples, not passwords, keys, tokens, nonces, or security decisions.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand((unsigned)time(NULL));
int die = rand() % 6 + 1; // Small demonstration only; may have modulo bias
printf("%d\n", die);
}

Use the operating system’s cryptographically secure random interface for security-sensitive values; that interface is outside ISO C.

#include <stdio.h>
#include <stdlib.h>
static int compare_ints(const void *left, const void *right) {
int a = *(const int *)left;
int b = *(const int *)right;
return (a > b) - (a < b); // Avoid subtraction overflow
}
int main(void) {
int values[] = {7, 2, 9, 2, -1};
size_t count = sizeof values / sizeof values[0];
qsort(values, count, sizeof values[0], compare_ints);
for (size_t i = 0; i < count; ++i) {
printf("%d%s", values[i], i + 1 == count ? "\n" : " ");
}
}

The input array must already be sorted under the same comparison ordering:

int key = 7;
int *found = bsearch(&key, values, count, sizeof values[0], compare_ints);
if (found != NULL) {
puts("found");
}

On many Unix-like toolchains, math functions require linking with -lm:

Terminal window
cc -std=c17 geometry.c -lm -o geometry
Function familyExamples
Roots and powerssqrt, cbrt, pow, hypot
Exponential/logarithmicexp, log, log10, log2
Trigonometricsin, cos, tan, atan2
Roundingfloor, ceil, trunc, round, nearbyint
Absolute/remainderfabs, fmod, remainder
Decomposition/scalingfrexp, ldexp, modf, scalbn
Classificationisfinite, isinf, isnan, fpclassify, signbit

Functions normally use double; suffix f selects float and suffix l selects long double, such as sqrtf and sqrtl.

#include <math.h>
#include <stdio.h>
double distance(double x, double y) {
return hypot(x, y);
}
int main(void) {
double value = sqrt(2.0);
if (isfinite(value)) {
printf("%.17g\n", value);
}
}
TopicGuidance
EqualityMany computed decimals cannot be compared reliably using exact ==
PrecisionUse DBL_EPSILON from <float.h> to understand representation, not as a universal tolerance
NaNComparisons involving a NaN are unusual; test using isnan
OverflowResults can become infinity; use isfinite when required
Domain errorsCalls such as sqrt(-1.0) may report domain issues; requirements depend on the math environment

Function/typePurpose
time_tCalendar-time representation
time(NULL)Obtain current calendar time where available
struct tmBroken-down calendar fields
gmtimeConvert to UTC broken-down time
localtimeConvert using local time zone
mktimeConvert local broken-down time back to time_t
difftimeDifference in seconds between calendar times
strftimeFormat a struct tm into text
clockProcessor time used by the program, not wall-clock timing
timespec_getC11 calendar time into struct timespec
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *local = localtime(&now);
char text[64];
if (local == NULL ||
strftime(text, sizeof text, "%Y-%m-%d %H:%M:%S", local) == 0) {
return 1;
}
puts(text);
}

localtime and gmtime may share internal storage overwritten by a later conversion. POSIX provides localtime_r/gmtime_r; Microsoft provides differently ordered _s variants. Copy the struct tm promptly or use the relevant platform wrapper in concurrent code.

The program begins in the "C" locale. Locale affects facilities such as character classification, collation, numeric formatting, and formatted input/output.

#include <locale.h>
if (setlocale(LC_ALL, "") == NULL) {
/* requested environment locale was unavailable */
}

Changing global locale can make reusable libraries and threaded programs surprising. Decide explicitly whether a program needs localized behavior or stable machine-readable formatting.

HeaderHandles
<wchar.h>Wide characters/strings, multibyte conversions, wide streams
<wctype.h>Wide character classification
<uchar.h>char16_t/char32_t conversion facilities in C11

The C library’s multibyte behavior depends on the active locale. A byte-oriented UTF-8 application often uses a dedicated Unicode library for normalization, grapheme handling, case folding, and robust validation.


<stdarg.h> lets a function accept an argument list whose types are specified by a convention, commonly a format string.

#include <stdarg.h>
#include <stdio.h>
static void log_error(const char *format, ...) {
va_list args;
fputs("error: ", stderr);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputc('\n', stderr);
}
int main(void) {
log_error("could not open %s (attempt %d)", "config.ini", 2);
}
Macro/functionMeaning
va_listIterator state for unnamed arguments
va_start(args, last_named)Begin consuming unnamed arguments
va_arg(args, type)Obtain next argument using its promoted type
va_copy(dst, src)C99 duplicate iterator state before consuming separately
va_end(args)Finish each initialized/copied iterator
vprintf, vfprintf, vsnprintfForward an existing va_list to formatting

Variadic arguments carry no automatic type metadata. The caller and function must agree exactly through a protocol such as a format string.


ISO C exposes a small portable signal interface:

#include <signal.h>
static volatile sig_atomic_t stop_requested = 0;
static void request_stop(int signal_number) {
(void)signal_number;
stop_requested = 1;
}
int main(void) {
if (signal(SIGINT, request_stop) == SIG_ERR) {
return 1;
}
while (!stop_requested) {
/* perform work in small units */
}
}

Inside a signal handler, keep operations minimal. Assigning to a volatile sig_atomic_t object is the central portable pattern; ordinary allocation and stdio operations are not safe assumptions there. POSIX programs usually use the more capable sigaction interface.

setjmp records an execution environment; longjmp returns to it without normal call-stack unwinding:

#include <setjmp.h>
jmp_buf recovery_point;
if (setjmp(recovery_point) == 0) {
/* work that may call longjmp(recovery_point, 1) */
} else {
/* recovery path */
}

Use this facility sparingly. It makes ownership and cleanup harder, and automatic local values changed after setjmp require special care.


<stdatomic.h> supplies operations for data intentionally shared between threads without ordinary data races:

#include <stdatomic.h>
atomic_uint requests = 0;
void note_request(void) {
atomic_fetch_add(&requests, 1);
}
NameMeaning
atomic_int, atomic_uintConvenient atomic integer types
atomic_initInitialize an atomic object where needed
atomic_load, atomic_storeRead/write atomically
atomic_fetch_addAtomic read-modify-write
atomic_compare_exchange_strongConditional update
memory_order_*Explicit ordering controls for advanced synchronization

Default sequential consistency is easier to reason about. Use weaker memory ordering only with a proven synchronization design.

The C11 <threads.h> interface is optional in implementations, so some platforms use POSIX threads or Windows threading APIs instead.

#if !defined(__STDC_NO_THREADS__)
#include <stdio.h>
#include <threads.h>
static int worker(void *unused) {
(void)unused;
puts("working");
return 0;
}
int main(void) {
thrd_t thread;
int result;
if (thrd_create(&thread, worker, NULL) != thrd_success) {
return 1;
}
if (thrd_join(thread, &result) != thrd_success) {
return 1;
}
return result;
}
#endif
FacilityPurpose
thrd_create, thrd_joinRun/join a thread
mtx_t, mtx_init, mtx_lock, mtx_unlockMutual exclusion
cnd_t, cnd_wait, cnd_signalCondition variables
tss_tThread-specific storage
call_onceOne-time initialization

Library calls that mutate shared global state, such as global locale changes, require special design in threaded programs.


16. Portability: ISO C, POSIX, and Extensions

Section titled “16. Portability: ISO C, POSIX, and Extensions”
NeedISO C optionCommon non-ISO option
Read bounded linefgetsPOSIX getline dynamically grows a line buffer
Duplicate stringAllocate plus memcpyPOSIX strdup
Thread-safe time conversionCopy promptly / synchronizePOSIX localtime_r, Microsoft localtime_s
Environment lookupgetenvModification APIs differ by platform
Processes/pipes/directories/socketsNot generally supplied by ISO CPOSIX or Windows APIs
Secure random bytesNot supplied by ISO COperating-system APIs

Prefer configuration tests in a build system over guessing from operating-system macros. When using POSIX APIs, request the appropriate declarations before system headers on relevant systems:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

Project configuration tools such as Autoconf or CMake can check whether functions and headers really exist for the selected compiler and target.

FunctionPurposeWarning
getenv(name)Read environment variableReturned storage must not be modified; subsequent environment operations may invalidate it
system(command)Invoke implementation command processorAvoid with untrusted input; quoting and behavior are platform-specific

17. Undefined Behavior and Security Checklist

Section titled “17. Undefined Behavior and Security Checklist”

The library cannot protect a program whose arguments violate an API contract. In C, a bug can silently become memory corruption or exploitable behavior.

AvoidPrefer
getsNever use it; read with fgets or an appropriate platform line reader
sprintf into fixed buffersChecked snprintf
strcpy/strcat with unknown lengthsSize first or construct using checked formatting
printf(user_input)printf("%s", user_input)
atoi for validated inputstrtol family with end/range checks
while (!feof(fp))Loop on fgets or fread result
tolower(ch) for possibly negative chartolower((unsigned char)ch)
memcpy with overlapping regionsmemmove
qsort comparator return a - b;(a > b) - (a < b)
ptr = realloc(ptr, size);Assign through a temporary pointer
Multiplying allocation dimensions uncheckedCheck against SIZE_MAX before allocation
Assuming sizeof(int) == 4Use limits/fixed-width types only where required and available
fflush(stdin)Handle input by reading and parsing explicitly
rand() for secretsPlatform secure randomness
Terminal window
cc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -g app.c -o app
cc -std=c17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined -g app.c -o app

For optimized deployments on toolchains that support them, hardening flags may add defenses around some libc misuse. They supplement correct bounds and ownership logic; they do not replace it.


#include <stdint.h>
#include <stdlib.h>
#include <string.h>
char *copy_string(const char *source) {
size_t length = strlen(source);
if (length == SIZE_MAX) {
return NULL;
}
char *copy = malloc(length + 1);
if (copy != NULL) {
memcpy(copy, source, length + 1);
}
return copy;
}

For this particular expression, strlen(source) == SIZE_MAX cannot describe a usable C object in practice, but the guard documents the + 1 size rule and keeps the pattern applicable to computed lengths.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
char *read_text(FILE *fp) {
size_t used = 0;
size_t cap = 4096;
char *data = malloc(cap);
if (data == NULL) {
return NULL;
}
for (;;) {
if (used + 1 == cap) {
if (cap > SIZE_MAX / 2) {
free(data);
return NULL;
}
size_t new_cap = cap * 2;
char *new_data = realloc(data, new_cap);
if (new_data == NULL) {
free(data);
return NULL;
}
data = new_data;
cap = new_cap;
}
size_t n = fread(data + used, 1, cap - used - 1, fp);
used += n;
if (n == 0) {
if (ferror(fp)) {
free(data);
return NULL;
}
data[used] = '\0';
return data;
}
}
}

This text helper appends '\0'. For arbitrary binary files, return both a byte buffer and its explicit length instead of treating data as a string.

#include <ctype.h>
#include <string.h>
int split_setting(char *line, char **key, char **value) {
line[strcspn(line, "\r\n")] = '\0';
char *equals = strchr(line, '=');
if (equals == NULL) {
return 0;
}
*equals = '\0';
char *left = line;
char *right = equals + 1;
while (isspace((unsigned char)*left)) {
++left;
}
while (isspace((unsigned char)*right)) {
++right;
}
char *end = left + strlen(left);
while (end != left && isspace((unsigned char)end[-1])) {
*--end = '\0';
}
*key = left;
*value = right;
return *left != '\0';
}
#include <stdint.h>
#include <stdlib.h>
struct int_vector {
int *items;
size_t length;
size_t capacity;
};
int int_vector_push(struct int_vector *v, int value) {
if (v->length == v->capacity) {
size_t next = v->capacity == 0 ? 8 : v->capacity * 2;
if (next < v->capacity || next > SIZE_MAX / sizeof *v->items) {
return 0;
}
int *items = realloc(v->items, next * sizeof *items);
if (items == NULL) {
return 0;
}
v->items = items;
v->capacity = next;
}
v->items[v->length++] = value;
return 1;
}
void int_vector_destroy(struct int_vector *v) {
free(v->items);
*v = (struct int_vector){0};
}

TaskStart with
Print a messageputs, fprintf, printf
Build bounded textsnprintf
Read a linefgets
Parse a checked integerstrtol, strtoul, strtoll, strtoull
Compare/search textstrcmp, strchr, strstr
Move bytes safelymemmove
Allocate an arraycalloc, checked malloc
Grow allocated storagechecked temporary-pointer realloc pattern
Read/write binary datafread, fwrite
Report system/library failureReturn check plus perror/strerror where documented
Sort/search an arrayqsort, bsearch
Work with floating point<math.h>, <float.h>
Format datestime, localtime/gmtime, strftime
Handle debug invariantsassert
Portable fixed-width formatting<stdint.h> plus <inttypes.h>
Share counters across threadsC11 <stdatomic.h> where supported

  1. Compile a program using puts, printf, fgets, and checked return values.
  2. Practice C strings and distinguish null-terminated text from arbitrary byte buffers.
  3. Parse lines using strtol/strtod rather than depending on unchecked conversion.
  4. Write file-copy and line-processing programs with correct EOF/error handling.
  5. Master allocation ownership, checked growth, and sanitizer-assisted testing.
  6. Use qsort, callbacks, math, and time formatting in small utilities.
  7. Separate ISO C from POSIX or Windows features when designing portable code.
  8. Learn atomics, thread synchronization, locale/encoding choices, and build hardening for production programs.
Terminal window
cc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -g program.c -o program

When the program uses <math.h> functions on Unix-like systems:

Terminal window
cc -std=c17 -Wall -Wextra -Wpedantic -g program.c -lm -o program

The best libc habit is simple: know each function’s contract, pass it valid buffers and types, and check every result that can tell you work failed.