C — Standard Library
The C Standard Library is a collection of headers and function implementations that provide foundational capabilities: I/O, string manipulation, memory management, math, time, character classification, and more. Every conforming C compiler provides these headers, making portable code possible across platforms.
This page covers the major headers in depth with practical examples. Understanding the standard library is essential — not only because you'll use these functions daily, but because many subtle bugs arise from misuse of standard library functions (buffer overflows in string functions, unbounded format strings, locale-dependent behavior).
The printf family converts values to text according to format specifiers. These are among the most frequently used C functions. Understanding format specifiers, width modifiers, and the difference between stack-based and va_list versions is critical.
| 1 | #include <stdio.h> |
| 2 | #include <stdarg.h> |
| 3 | |
| 4 | int main(void) { |
| 5 | int age = 30; |
| 6 | double pi = 3.14159265358979; |
| 7 | const char *name = "Alice"; |
| 8 | |
| 9 | // printf: basic format specifiers |
| 10 | printf("Name: %s, Age: %d, Pi: %.2f\n", name, age, pi); |
| 11 | |
| 12 | // Width and padding |
| 13 | printf("[%10s]\n", "right"); // [ right] |
| 14 | printf("[%-10s]\n", "left"); // [left ] |
| 15 | printf("[%08d]\n", 42); // [00000042] |
| 16 | printf("[%#010x]\n", 255); // [0x000000ff] |
| 17 | |
| 18 | // fprintf: write to a FILE stream |
| 19 | FILE *log = fopen("output.log", "w"); |
| 20 | if (log) { |
| 21 | fprintf(log, "[%s] Level: %d\n", "INFO", 42); |
| 22 | fclose(log); |
| 23 | } |
| 24 | |
| 25 | // snprintf: write to a buffer with size limit (SAFE) |
| 26 | char buf[64]; |
| 27 | int written = snprintf(buf, sizeof(buf), "Hello, %s! You are %d.", name, age); |
| 28 | printf("Buffer: %s (would need %d chars)\n", buf, written); |
| 29 | |
| 30 | // sprintf: UNSAFE -- no bounds checking (avoid!) |
| 31 | // sprintf(buf, "This could overflow: %s", long_string); |
| 32 | |
| 33 | // Return value: number of characters written (excluding null terminator) |
| 34 | // Or negative value on error |
| 35 | int ret = printf("Return value test\n"); |
| 36 | printf("printf returned: %d\n", ret); |
| 37 | |
| 38 | return 0; |
| 39 | } |
Common Format Specifiers:
| Specifier | Type | Example |
|---|---|---|
| %d, %i | int (signed decimal) | %d → 42 |
| %u | unsigned int | %u → 42 |
| %ld | long | %ld → 100000L |
| %f, %lf | double | %.2f → 3.14 |
| %s | char * | %s → hello |
| %p | void * | %p → 0x7fff5e8 |
| %x, %X | hexadecimal | %x → ff |
| %zu | size_t | %zu → 64 |
vprintf for variadic wrappers:
| 1 | #include <stdio.h> |
| 2 | #include <stdarg.h> |
| 3 | |
| 4 | // Custom logging function using vprintf |
| 5 | void log_message(const char *level, const char *fmt, ...) { |
| 6 | va_list args; |
| 7 | fprintf(stderr, "[%s] ", level); |
| 8 | va_start(args, fmt); |
| 9 | vfprintf(stderr, fmt, args); |
| 10 | va_end(args); |
| 11 | fprintf(stderr, "\n"); |
| 12 | } |
| 13 | |
| 14 | // Custom snprintf-like function using vsnprintf |
| 15 | int format_string(char *buf, size_t size, const char *fmt, ...) { |
| 16 | va_list args; |
| 17 | va_start(args, fmt); |
| 18 | int result = vsnprintf(buf, size, fmt, args); |
| 19 | va_end(args); |
| 20 | return result; |
| 21 | } |
| 22 | |
| 23 | int main(void) { |
| 24 | log_message("INFO", "User %s logged in from %s", "admin", "192.168.1.1"); |
| 25 | log_message("ERROR", "Connection failed: %d attempts remaining", 3); |
| 26 | |
| 27 | char msg[128]; |
| 28 | format_string(msg, sizeof(msg), "Temperature: %.1f degrees", 72.5); |
| 29 | printf("%s\n", msg); |
| 30 | |
| 31 | return 0; |
| 32 | } |
The scanf family reads formatted input. While useful for simple parsing, scanf is notoriously difficult to use correctly due to buffer overflow risks, whitespace handling, and error recovery. fgets + sscanf is often the safer pattern.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | int main(void) { |
| 5 | int age; |
| 6 | char name[64]; |
| 7 | double score; |
| 8 | |
| 9 | // scanf: reads until whitespace, leaves newline in buffer |
| 10 | printf("Enter name: "); |
| 11 | // %63s limits read to 63 chars + null terminator (prevents overflow) |
| 12 | scanf("%63s", name); |
| 13 | |
| 14 | printf("Enter age: "); |
| 15 | scanf("%d", &age); |
| 16 | |
| 17 | // DANGER: leaving newline in input buffer can cause problems |
| 18 | // Clear the leftover newline |
| 19 | int c; |
| 20 | while ((c = getchar()) != '\n' && c != EOF); |
| 21 | |
| 22 | printf("Enter score: "); |
| 23 | scanf("%lf", &score); |
| 24 | |
| 25 | printf("Name: %s, Age: %d, Score: %.1f\n", name, age, score); |
| 26 | |
| 27 | // fgets + sscanf: the SAFE pattern for line-based input |
| 28 | char line[256]; |
| 29 | printf("Enter data (name age score): "); |
| 30 | if (fgets(line, sizeof(line), stdin)) { |
| 31 | if (sscanf(line, "%63s %d %lf", name, &age, &score) == 3) { |
| 32 | printf("Parsed: %s, %d, %.1f\n", name, age, score); |
| 33 | } else { |
| 34 | fprintf(stderr, "Invalid input format\n"); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | // Reading entire lines safely |
| 39 | printf("Enter a line: "); |
| 40 | if (fgets(line, sizeof(line), stdin)) { |
| 41 | // Remove trailing newline |
| 42 | line[strcspn(line, "\n")] = '\0'; |
| 43 | printf("You entered: [%s] (length: %zu)\n", |
| 44 | line, strlen(line)); |
| 45 | } |
| 46 | |
| 47 | return 0; |
| 48 | } |
warning
scanf("%s", buf) without a width limit — it will overflow your buffer. Always use %Ns where N is one less than your buffer size. Better yet, prefer fgets() + sscanf() for line-based input.File I/O in C is built around the FILE stream abstraction. Files are opened with fopen(), which returns a FILE pointer. All subsequent operations use this pointer, and the file is closed with fclose(). Binary I/O uses fread/fwrite; text I/O uses fprintf/fscanf.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | // Text file I/O |
| 6 | void text_file_example(void) { |
| 7 | // Write text file |
| 8 | FILE *fp = fopen("example.txt", "w"); |
| 9 | if (!fp) { |
| 10 | perror("fopen for write"); |
| 11 | return; |
| 12 | } |
| 13 | fprintf(fp, "Line 1: Hello World\n"); |
| 14 | fprintf(fp, "Line 2: %d + %d = %d\n", 3, 4, 7); |
| 15 | fprintf(fp, "Line 3: Pi is %.6f\n", 3.141593); |
| 16 | fclose(fp); |
| 17 | |
| 18 | // Read text file line by line |
| 19 | fp = fopen("example.txt", "r"); |
| 20 | if (!fp) { |
| 21 | perror("fopen for read"); |
| 22 | return; |
| 23 | } |
| 24 | char line[256]; |
| 25 | int line_num = 0; |
| 26 | while (fgets(line, sizeof(line), fp)) { |
| 27 | line_num++; |
| 28 | printf("Line %d: %s", line_num, line); |
| 29 | } |
| 30 | fclose(fp); |
| 31 | } |
| 32 | |
| 33 | // Binary file I/O |
| 34 | void binary_file_example(void) { |
| 35 | typedef struct { |
| 36 | int id; |
| 37 | double value; |
| 38 | char name[32]; |
| 39 | } Record; |
| 40 | |
| 41 | Record records[] = { |
| 42 | {1, 3.14, "pi"}, |
| 43 | {2, 2.718, "e"}, |
| 44 | {3, 1.414, "sqrt2"} |
| 45 | }; |
| 46 | int count = sizeof(records) / sizeof(records[0]); |
| 47 | |
| 48 | // Write binary file |
| 49 | FILE *fp = fopen("records.bin", "wb"); |
| 50 | if (!fp) return; |
| 51 | fwrite(records, sizeof(Record), count, fp); |
| 52 | fclose(fp); |
| 53 | |
| 54 | // Read binary file |
| 55 | Record loaded[10]; |
| 56 | fp = fopen("records.bin", "rb"); |
| 57 | if (!fp) return; |
| 58 | size_t read_count = fread(loaded, sizeof(Record), 10, fp); |
| 59 | fclose(fp); |
| 60 | |
| 61 | printf("Loaded %zu records\n", read_count); |
| 62 | for (size_t i = 0; i < read_count; i++) { |
| 63 | printf(" [%d] %s = %.3f\n", |
| 64 | loaded[i].id, loaded[i].name, loaded[i].value); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // File seeking |
| 69 | void file_seek_example(void) { |
| 70 | FILE *fp = fopen("seek_test.bin", "w+b"); |
| 71 | if (!fp) return; |
| 72 | |
| 73 | // Write 10 integers |
| 74 | for (int i = 0; i < 10; i++) { |
| 75 | fwrite(&i, sizeof(int), 1, fp); |
| 76 | } |
| 77 | |
| 78 | // Seek to position 5 * sizeof(int) and read |
| 79 | fseek(fp, 5 * sizeof(int), SEEK_SET); |
| 80 | int val; |
| 81 | fread(&val, sizeof(int), 1, fp); |
| 82 | printf("Value at position 5: %d\n", val); |
| 83 | |
| 84 | // Seek from end |
| 85 | fseek(fp, -2 * sizeof(int), SEEK_END); |
| 86 | fread(&val, sizeof(int), 1, fp); |
| 87 | printf("Second to last: %d\n", val); |
| 88 | |
| 89 | // ftell returns current position |
| 90 | printf("Current position: %ld\n", ftell(fp)); |
| 91 | |
| 92 | // rewind resets to beginning |
| 93 | rewind(fp); |
| 94 | printf("After rewind: %ld\n", ftell(fp)); |
| 95 | |
| 96 | fclose(fp); |
| 97 | } |
| 98 | |
| 99 | // Character I/O |
| 100 | void character_io_example(void) { |
| 101 | // Write characters one at a time |
| 102 | FILE *fp = fopen("chars.txt", "w"); |
| 103 | if (!fp) return; |
| 104 | for (int c = 'A'; c <= 'Z'; c++) { |
| 105 | fputc(c, fp); |
| 106 | } |
| 107 | fclose(fp); |
| 108 | |
| 109 | // Read characters one at a time |
| 110 | fp = fopen("chars.txt", "r"); |
| 111 | if (!fp) return; |
| 112 | int c; |
| 113 | while ((c = fgetc(fp)) != EOF) { |
| 114 | putchar(c); |
| 115 | } |
| 116 | putchar('\n'); |
| 117 | fclose(fp); |
| 118 | } |
| 119 | |
| 120 | int main(void) { |
| 121 | text_file_example(); |
| 122 | binary_file_example(); |
| 123 | file_seek_example(); |
| 124 | character_io_example(); |
| 125 | return 0; |
| 126 | } |
| Mode | Description | File must exist |
|---|---|---|
| "r" | Read only | Yes |
| "w" | Write (truncate or create) | No |
| "a" | Append (create if needed) | No |
| "r+" | Read + write (truncate) | Yes |
| "rb", "wb" | Binary mode | Depends on prefix |
info
| 1 | #include <stdio.h> |
| 2 | #include <errno.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | int main(void) { |
| 6 | FILE *fp = fopen("/nonexistent/file.txt", "r"); |
| 7 | if (!fp) { |
| 8 | // errno is set by the failed system call |
| 9 | fprintf(stderr, "Error %d: %s\n", errno, strerror(errno)); |
| 10 | perror("fopen failed"); // Prints: "fopen failed: No such file or directory" |
| 11 | } |
| 12 | |
| 13 | // errno is a thread-local integer set by system calls on error |
| 14 | // Always check errno immediately after the call that failed |
| 15 | // before calling another function (which may overwrite errno) |
| 16 | |
| 17 | return 0; |
| 18 | } |
stdlib.h provides dynamic memory allocation (malloc, calloc, realloc, free), random number generation, sorting with function pointers, and program control. See the Memory Management page for detailed allocation coverage — here we focus on qsort, bsearch, and other utilities.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <time.h> |
| 4 | #include <string.h> |
| 5 | |
| 6 | // qsort comparator: returns <0, 0, or >0 |
| 7 | int compare_ints(const void *a, const void *b) { |
| 8 | return (*(int *)a - *(int *)b); |
| 9 | } |
| 10 | |
| 11 | int compare_strings(const void *a, const void *b) { |
| 12 | return strcmp(*(const char **)a, *(const char **)b); |
| 13 | } |
| 14 | |
| 15 | // For descending order |
| 16 | int compare_ints_desc(const void *a, const void *b) { |
| 17 | return (*(int *)b - *(int *)a); |
| 18 | } |
| 19 | |
| 20 | // Struct comparator by field |
| 21 | typedef struct { |
| 22 | char name[32]; |
| 23 | int score; |
| 24 | } Student; |
| 25 | |
| 26 | int compare_students_by_score(const void *a, const void *b) { |
| 27 | const Student *sa = (const Student *)a; |
| 28 | const Student *sb = (const Student *)b; |
| 29 | return sb->score - sa->score; // descending |
| 30 | } |
| 31 | |
| 32 | // bsearch: binary search on sorted array (returns pointer or NULL) |
| 33 | int find_student(const void *key, const void *array) { |
| 34 | const Student *s = (const Student *)key; |
| 35 | const Student *arr = (const Student *)array; |
| 36 | // Compare by score for binary search on sorted array |
| 37 | return s->score - arr->score; |
| 38 | } |
| 39 | |
| 40 | int main(void) { |
| 41 | // Random numbers |
| 42 | srand((unsigned int)time(NULL)); |
| 43 | printf("5 random numbers: "); |
| 44 | for (int i = 0; i < 5; i++) { |
| 45 | printf("%d ", rand() % 100); |
| 46 | } |
| 47 | printf("\n"); |
| 48 | |
| 49 | // qsort: array of integers |
| 50 | int nums[] = {42, 17, 93, 8, 55, 21, 76, 34}; |
| 51 | size_t n = sizeof(nums) / sizeof(nums[0]); |
| 52 | qsort(nums, n, sizeof(int), compare_ints); |
| 53 | printf("Sorted ascending: "); |
| 54 | for (size_t i = 0; i < n; i++) printf("%d ", nums[i]); |
| 55 | printf("\n"); |
| 56 | |
| 57 | qsort(nums, n, sizeof(int), compare_ints_desc); |
| 58 | printf("Sorted descending: "); |
| 59 | for (size_t i = 0; i < n; i++) printf("%d ", nums[i]); |
| 60 | printf("\n"); |
| 61 | |
| 62 | // qsort: array of strings |
| 63 | const char *fruits[] = {"banana", "apple", "cherry", "date"}; |
| 64 | size_t nf = sizeof(fruits) / sizeof(fruits[0]); |
| 65 | qsort(fruits, nf, sizeof(char *), compare_strings); |
| 66 | printf("Sorted fruits: "); |
| 67 | for (size_t i = 0; i < nf; i++) printf("%s ", fruits[i]); |
| 68 | printf("\n"); |
| 69 | |
| 70 | // bsearch: find element in sorted array |
| 71 | int target = 42; |
| 72 | int *found = bsearch(&target, nums, n, sizeof(int), compare_ints); |
| 73 | if (found) printf("Found %d in sorted array\n", *found); |
| 74 | |
| 75 | // Sorting students by score |
| 76 | Student students[] = { |
| 77 | {"Alice", 95}, {"Bob", 87}, {"Carol", 92}, {"Dave", 78} |
| 78 | }; |
| 79 | size_t ns = sizeof(students) / sizeof(students[0]); |
| 80 | qsort(students, ns, sizeof(Student), compare_students_by_score); |
| 81 | printf("Students by score:\n"); |
| 82 | for (size_t i = 0; i < ns; i++) { |
| 83 | printf(" %s: %d\n", students[i].name, students[i].score); |
| 84 | } |
| 85 | |
| 86 | // abs, labs, llabs |
| 87 | printf("|-42| = %d\n", abs(-42)); |
| 88 | printf("|-100L| = %ld\n", labs(-100L)); |
| 89 | |
| 90 | // String conversion |
| 91 | printf("atoi: %d\n", atoi("42")); |
| 92 | printf("atof: %.2f\n", atof("3.14")); |
| 93 | |
| 94 | char *endptr; |
| 95 | long val = strtol("123abc", &endptr, 10); |
| 96 | printf("strtol: %ld (stopped at: '%s')\n", val, endptr); |
| 97 | |
| 98 | // exit, abort, atexit |
| 99 | // atexit registers a function to run at exit |
| 100 | return 0; |
| 101 | } |
warning
qsort comparator must handle const void * parameters. Never cast to non-const. Also, avoid subtraction-based comparators for types where overflow is possible (e.g., comparing very large unsigned values). Use explicit if/else instead.| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | int main(void) { |
| 5 | // getenv: read environment variable |
| 6 | const char *home = getenv("HOME"); |
| 7 | if (home) { |
| 8 | printf("HOME = %s\n", home); |
| 9 | } |
| 10 | |
| 11 | const char *path = getenv("PATH"); |
| 12 | if (path) { |
| 13 | printf("PATH length: %zu\n", strlen(path)); |
| 14 | } |
| 15 | |
| 16 | // system: execute a shell command (use with caution!) |
| 17 | int result = system("echo 'Hello from shell'"); |
| 18 | printf("system returned: %d\n", result); |
| 19 | |
| 20 | // atoi, atof, strtol, strtod: string to number conversion |
| 21 | // strtol is the recommended way (provides error detection) |
| 22 | char *endptr; |
| 23 | |
| 24 | long i = strtol("42", &endptr, 10); |
| 25 | printf("strtol("42"): %ld\n", i); |
| 26 | |
| 27 | double d = strtod("3.14159", &endptr); |
| 28 | printf("strtod("3.14159"): %f\n", d); |
| 29 | |
| 30 | // Base 16 (hex) |
| 31 | long hex = strtol("0xFF", &endptr, 16); |
| 32 | printf("strtol("0xFF", base=16): %ld\n", hex); |
| 33 | |
| 34 | // Error detection with strtol |
| 35 | errno = 0; |
| 36 | long big = strtol("not_a_number", &endptr, 10); |
| 37 | if (errno != 0 || *endptr != '\0') { |
| 38 | printf("Conversion failed (errno=%d, leftover='%s')\n", |
| 39 | errno, endptr); |
| 40 | } |
| 41 | |
| 42 | return 0; |
| 43 | } |
The string.h header provides functions for string manipulation and memory operations. C strings are null-terminated byte arrays — there is no length field, so strlen() must scan the entire string. Many string functions are unsafe by design (no bounds checking), making the bounded variants essential.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | int main(void) { |
| 5 | // strlen: length (excluding null terminator) |
| 6 | char hello[] = "Hello, World!"; |
| 7 | printf("strlen: %zu\n", strlen(hello)); // 13 |
| 8 | |
| 9 | // strcpy / strncpy: copy |
| 10 | char dst[64]; |
| 11 | strcpy(dst, hello); // Unsafe: no bounds check |
| 12 | printf("strcpy: %s\n", dst); |
| 13 | |
| 14 | // Safe version: strncpy + manual null termination |
| 15 | char safe[16]; |
| 16 | strncpy(safe, hello, sizeof(safe) - 1); |
| 17 | safe[sizeof(safe) - 1] = '\0'; |
| 18 | printf("strncpy: %s\n", safe); |
| 19 | |
| 20 | // strcat / strncat: concatenate |
| 21 | char greeting[128] = "Hello"; |
| 22 | strncat(greeting, ", ", sizeof(greeting) - strlen(greeting) - 1); |
| 23 | strncat(greeting, "World!", sizeof(greeting) - strlen(greeting) - 1); |
| 24 | printf("strncat: %s\n", greeting); |
| 25 | |
| 26 | // strcmp / strncmp: compare |
| 27 | printf("strcmp: %d\n", strcmp("abc", "abd")); // negative |
| 28 | printf("strcmp: %d\n", strcmp("abc", "abc")); // 0 |
| 29 | printf("strncmp: %d\n", strncmp("abcde", "abc", 3)); // 0 (first 3 match) |
| 30 | |
| 31 | // strchr / strrchr: find character |
| 32 | const char *path = "/usr/local/bin/program"; |
| 33 | const char *last_slash = strrchr(path, '/'); |
| 34 | if (last_slash) printf("Last component: %s\n", last_slash + 1); |
| 35 | |
| 36 | const char *first_slash = strchr(path, '/'); |
| 37 | if (first_slash) printf("After first slash: %s\n", first_slash + 1); |
| 38 | |
| 39 | // strstr: find substring |
| 40 | const char *text = "the quick brown fox jumps over the lazy dog"; |
| 41 | const char *fox = strstr(text, "fox"); |
| 42 | if (fox) printf("Found 'fox' at offset: %ld\n", fox - text); |
| 43 | |
| 44 | // strtok: tokenize (modifies the input string!) |
| 45 | char input[] = "one,two,three,four"; |
| 46 | char *token = strtok(input, ","); |
| 47 | while (token != NULL) { |
| 48 | printf("Token: %s\n", token); |
| 49 | token = strtok(NULL, ","); // Continue tokenizing |
| 50 | } |
| 51 | |
| 52 | // strspn / strcspn: span of characters |
| 53 | const char *s = " hello world "; |
| 54 | size_t leading = strspn(s, " \t\n"); |
| 55 | printf("Leading whitespace: %zu chars\n", leading); |
| 56 | |
| 57 | // strdup: duplicate string (POSIX, commonly available) |
| 58 | char *dup = strdup("Allocated copy"); |
| 59 | if (dup) { |
| 60 | printf("strdup: %s\n", dup); |
| 61 | free(dup); // Must free! |
| 62 | } |
| 63 | |
| 64 | return 0; |
| 65 | } |
warning
The memory functions (memset, memcpy, memmove, memcmp) operate on raw bytes regardless of type. They are faster than their string counterparts because they don't search for null terminators and handle arbitrary byte sequences.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | int main(void) { |
| 5 | // memset: fill memory with a byte value |
| 6 | int arr[10]; |
| 7 | memset(arr, 0, sizeof(arr)); // Zero out entire array |
| 8 | memset(arr, 0xFF, 5 * sizeof(int)); // Set first 5 ints to 0xFF |
| 9 | |
| 10 | // memcpy: copy non-overlapping memory regions |
| 11 | int src[] = {10, 20, 30, 40, 50}; |
| 12 | int dst[5]; |
| 13 | memcpy(dst, src, sizeof(src)); |
| 14 | printf("memcpy: "); |
| 15 | for (int i = 0; i < 5; i++) printf("%d ", dst[i]); |
| 16 | printf("\n"); |
| 17 | |
| 18 | // memmove: copy potentially overlapping memory (SAFE) |
| 19 | // memcpy is undefined if src and dst overlap |
| 20 | int overlap[] = {1, 2, 3, 4, 5, 6, 7, 8}; |
| 21 | // Shift elements right by 2: {1, 2, 1, 2, 3, 4, 5, 6} |
| 22 | memmove(overlap + 2, overlap, 6 * sizeof(int)); |
| 23 | printf("memmove: "); |
| 24 | for (int i = 0; i < 8; i++) printf("%d ", overlap[i]); |
| 25 | printf("\n"); |
| 26 | |
| 27 | // memcmp: compare memory byte-by-byte |
| 28 | int a[] = {1, 2, 3}; |
| 29 | int b[] = {1, 2, 4}; |
| 30 | int result = memcmp(a, b, sizeof(int) * 3); |
| 31 | printf("memcmp: %d\n", result); // Negative (3 < 4) |
| 32 | |
| 33 | // Practical: zeroing a struct |
| 34 | typedef struct { |
| 35 | int id; |
| 36 | double values[100]; |
| 37 | char name[64]; |
| 38 | } LargeStruct; |
| 39 | |
| 40 | LargeStruct s; |
| 41 | memset(&s, 0, sizeof(s)); // Zero everything |
| 42 | s.id = 1; |
| 43 | printf("Struct id: %d, first value: %f\n", s.id, s.values[0]); |
| 44 | |
| 45 | // Practical: building a buffer |
| 46 | char packet[256]; |
| 47 | memset(packet, 0, sizeof(packet)); |
| 48 | size_t offset = 0; |
| 49 | |
| 50 | // Header: 4 bytes |
| 51 | int header = 0x48656C6C; // "Hell" in ASCII |
| 52 | memcpy(packet + offset, &header, sizeof(header)); |
| 53 | offset += sizeof(header); |
| 54 | |
| 55 | // Payload |
| 56 | const char *payload = "Hello from C!"; |
| 57 | size_t payload_len = strlen(payload); |
| 58 | memcpy(packet + offset, payload, payload_len); |
| 59 | offset += payload_len; |
| 60 | |
| 61 | printf("Packet: %zu bytes\n", offset); |
| 62 | |
| 63 | return 0; |
| 64 | } |
best practice
The math.h header provides double-precision floating-point math functions. Compile with-lm on Linux to link the math library. C99 and later provide the full set of functions including special values (HUGE_VAL, NAN) and error handling via errno and floating-point exception flags (fenv.h).
| 1 | #include <stdio.h> |
| 2 | #include <math.h> |
| 3 | #include <errno.h> |
| 4 | #include <fenv.h> |
| 5 | |
| 6 | int main(void) { |
| 7 | // Trigonometry |
| 8 | double angle_deg = 45.0; |
| 9 | double angle_rad = angle_deg * M_PI / 180.0; |
| 10 | printf("sin(45): %.4f\n", sin(angle_rad)); |
| 11 | printf("cos(45): %.4f\n", cos(angle_rad)); |
| 12 | printf("tan(45): %.4f\n", tan(angle_rad)); |
| 13 | |
| 14 | // Inverse trig |
| 15 | printf("asin(1): %.4f rad = %.1f deg\n", |
| 16 | asin(1.0), asin(1.0) * 180.0 / M_PI); |
| 17 | printf("atan2(1,1): %.4f\n", atan2(1.0, 1.0)); |
| 18 | |
| 19 | // Power and roots |
| 20 | printf("pow(2,10): %.0f\n", pow(2.0, 10.0)); |
| 21 | printf("sqrt(144): %.1f\n", sqrt(144.0)); |
| 22 | printf("cbrt(27): %.1f\n", cbrt(27.0)); |
| 23 | |
| 24 | // Logarithms and exponentials |
| 25 | printf("log(e): %.4f\n", log(M_E)); |
| 26 | printf("log2(1024): %.1f\n", log2(1024.0)); |
| 27 | printf("log10(1000): %.1f\n", log10(1000.0)); |
| 28 | printf("exp(1): %.4f\n", exp(1.0)); |
| 29 | |
| 30 | // Rounding |
| 31 | printf("ceil(2.3): %.1f\n", ceil(2.3)); // 3.0 |
| 32 | printf("floor(2.8): %.1f\n", floor(2.8)); // 2.0 |
| 33 | printf("round(2.5): %.1f\n", round(2.5)); // 3.0 (ties to even in C11) |
| 34 | printf("trunc(-3.7): %.1f\n", trunc(-3.7)); // -3.0 |
| 35 | |
| 36 | // Remainder |
| 37 | printf("fmod(7.5, 2.0): %.2f\n", fmod(7.5, 2.0)); |
| 38 | |
| 39 | // Min/max |
| 40 | printf("fmax(3,7): %.1f\n", fmax(3.0, 7.0)); |
| 41 | printf("fmin(3,7): %.1f\n", fmin(3.0, 7.0)); |
| 42 | |
| 43 | // Absolute value (floating point) |
| 44 | printf("fabs(-42.5): %.1f\n", fabs(-42.5)); |
| 45 | |
| 46 | // Constants |
| 47 | printf("M_PI = %.15f\n", M_PI); |
| 48 | printf("HUGE_VAL = %e\n", HUGE_VAL); |
| 49 | |
| 50 | // Error handling with errno |
| 51 | feclearexcept(FE_ALL_EXCEPT); |
| 52 | errno = 0; |
| 53 | double result = sqrt(-1.0); // Domain error |
| 54 | if (errno == EDOM) { |
| 55 | printf("sqrt(-1): domain error\n"); |
| 56 | } |
| 57 | |
| 58 | // Overflow |
| 59 | feclearexcept(FE_ALL_EXCEPT); |
| 60 | errno = 0; |
| 61 | result = exp(1000.0); |
| 62 | if (errno == ERANGE) { |
| 63 | printf("exp(1000): overflow -> HUGE_VAL\n"); |
| 64 | } |
| 65 | |
| 66 | return 0; |
| 67 | } |
| Function | Description | Domain Error |
|---|---|---|
| sqrt(x) | Square root | x < 0 |
| log(x) | Natural logarithm | x <= 0 |
| asin(x) | Arc sine | |x| > 1 |
| pow(x,y) | x raised to y | x<0, y not integer |
| exp(x) | e^x | Overflow → HUGE_VAL |
note
-lm on Linux/GCC to link the math library. On macOS/Clang and MSVC, math functions are linked automatically. C99 provides float versions (sinf, cosf, sqrtf, etc.) and long double versions (sinl, cosl, sqrtl) for different precision needs.The ctype.h header provides functions to classify and convert individual characters. These functions accept an int argument (the character as unsigned char or EOF) and return non-zero if the condition is true, or zero if false.
| 1 | #include <stdio.h> |
| 2 | #include <ctype.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | // Convert string to uppercase |
| 6 | void to_upper_string(char *str) { |
| 7 | for (size_t i = 0; str[i]; i++) { |
| 8 | str[i] = (char)toupper((unsigned char)str[i]); |
| 9 | } |
| 10 | } |
| 11 | |
| 12 | // Validate that a string contains only digits |
| 13 | int is_all_digits(const char *str) { |
| 14 | for (size_t i = 0; str[i]; i++) { |
| 15 | if (!isdigit((unsigned char)str[i])) return 0; |
| 16 | } |
| 17 | return 1; |
| 18 | } |
| 19 | |
| 20 | // Count character types in a string |
| 21 | void count_types(const char *str) { |
| 22 | int alpha = 0, digit = 0, space = 0, punct = 0, other = 0; |
| 23 | for (size_t i = 0; str[i]; i++) { |
| 24 | unsigned char c = (unsigned char)str[i]; |
| 25 | if (isalpha(c)) alpha++; |
| 26 | else if (isdigit(c)) digit++; |
| 27 | else if (isspace(c)) space++; |
| 28 | else if (ispunct(c)) punct++; |
| 29 | else other++; |
| 30 | } |
| 31 | printf("alpha=%d digit=%d space=%d punct=%d other=%d\n", |
| 32 | alpha, digit, space, punct, other); |
| 33 | } |
| 34 | |
| 35 | int main(void) { |
| 36 | // Classification functions |
| 37 | printf("isalpha('A'): %d\n", isalpha('A')); // Non-zero (true) |
| 38 | printf("isdigit('5'): %d\n", isdigit('5')); // Non-zero |
| 39 | printf("isalnum('z'): %d\n", isalnum('z')); // Non-zero |
| 40 | printf("isupper('A'): %d\n", isupper('A')); // Non-zero |
| 41 | printf("islower('a'): %d\n", islower('a')); // Non-zero |
| 42 | printf("isspace(' '): %d\n", isspace(' ')); // Non-zero |
| 43 | printf("isprint('!'): %d\n", isprint('!')); // Non-zero |
| 44 | printf("ispunct('@'): %d\n", ispunct('@')); // Non-zero |
| 45 | |
| 46 | // Conversion functions |
| 47 | printf("toupper('a'): %c\n", toupper('a')); // A |
| 48 | printf("tolower('Z'): %c\n", tolower('Z')); // z |
| 49 | |
| 50 | // Practical examples |
| 51 | char text[] = "Hello, World! 123"; |
| 52 | printf("Original: %s\n", text); |
| 53 | to_upper_string(text); |
| 54 | printf("Upper: %s\n", text); |
| 55 | |
| 56 | printf("'12345' is all digits: %s\n", |
| 57 | is_all_digits("12345") ? "yes" : "no"); |
| 58 | printf("'12a45' is all digits: %s\n", |
| 59 | is_all_digits("12a45") ? "yes" : "no"); |
| 60 | |
| 61 | count_types("Hello, World! 123"); |
| 62 | |
| 63 | return 0; |
| 64 | } |
info
unsigned char before passing to ctype.h functions. If char is signed (which is implementation-defined), negative values (other than EOF) cause undefined behavior. Use toupper((unsigned char)c) as the safe pattern.The time.h header provides functions for getting the current time, measuring elapsed time, and formatting dates. The main types are time_t (calendar time as seconds since epoch), clock_t (processor time), and struct tm (broken-down time components).
| 1 | #include <stdio.h> |
| 2 | #include <time.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | int main(void) { |
| 6 | // time(): current calendar time as time_t |
| 7 | time_t now = time(NULL); |
| 8 | printf("Epoch time: %ld\n", (long)now); |
| 9 | |
| 10 | // ctime(): convert time_t to human-readable string |
| 11 | printf("Current time: %s", ctime(&now)); |
| 12 | |
| 13 | // localtime(): convert to struct tm (broken-down local time) |
| 14 | struct tm *local = localtime(&now); |
| 15 | printf("Year: %d, Month: %d, Day: %d\n", |
| 16 | local->tm_year + 1900, local->tm_mon + 1, local->tm_mday); |
| 17 | printf("Hour: %d, Min: %d, Sec: %d\n", |
| 18 | local->tm_hour, local->tm_min, local->tm_sec); |
| 19 | printf("Day of week: %d (0=Sunday)\n", local->tm_wday); |
| 20 | printf("Day of year: %d\n", local->tm_yday); |
| 21 | |
| 22 | // gmtime(): convert to UTC |
| 23 | struct tm *utc = gmtime(&now); |
| 24 | printf("UTC: %04d-%02d-%02d %02d:%02d:%02d\n", |
| 25 | utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday, |
| 26 | utc->tm_hour, utc->tm_min, utc->tm_sec); |
| 27 | |
| 28 | // strftime(): format time into a string (SAFE, bounded) |
| 29 | char buf[128]; |
| 30 | strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z", local); |
| 31 | printf("Formatted: %s\n", buf); |
| 32 | |
| 33 | strftime(buf, sizeof(buf), "%A, %B %d, %Y", local); |
| 34 | printf("Long date: %s\n", buf); |
| 35 | |
| 36 | // mktime(): convert struct tm back to time_t |
| 37 | struct tm target = {0}; |
| 38 | target.tm_year = 2025 - 1900; // Years since 1900 |
| 39 | target.tm_mon = 0; // January (0-based) |
| 40 | target.tm_mday = 1; // 1st |
| 41 | target.tm_hour = 12; |
| 42 | time_t target_time = mktime(&target); |
| 43 | printf("New Year 2025: %s", ctime(&target_time)); |
| 44 | |
| 45 | // difftime(): difference between two time_t values |
| 46 | double diff = difftime(now, target_time); |
| 47 | printf("Seconds since Jan 1 2025: %.0f\n", diff); |
| 48 | |
| 49 | // clock(): CPU time used by the program |
| 50 | clock_t start = clock(); |
| 51 | // Simulate work |
| 52 | volatile long sum = 0; |
| 53 | for (long i = 0; i < 10000000L; i++) sum += i; |
| 54 | clock_t end = clock(); |
| 55 | double elapsed = (double)(end - start) / CLOCKS_PER_SEC; |
| 56 | printf("Elapsed CPU time: %.6f seconds\n", elapsed); |
| 57 | printf("CLOCKS_PER_SEC: %ld\n", (long)CLOCKS_PER_SEC); |
| 58 | |
| 59 | return 0; |
| 60 | } |
note
strftime() format specifiers are similar to printf: %Y (year), %m (month), %d (day),%H:%M:%S (time), %A (weekday name), %B (month name). See the strftime man page for the full list.The stdint.h header (C99) provides integer types with guaranteed widths. Use these when the exact size matters: protocol headers, binary file formats, network packets, or anywhere you need a specific number of bits.
| 1 | #include <stdio.h> |
| 2 | #include <stdint.h> |
| 3 | #include <inttypes.h> |
| 4 | #include <limits.h> |
| 5 | |
| 6 | int main(void) { |
| 7 | // Fixed-width signed integers |
| 8 | int8_t a = -128; // Exactly 1 byte |
| 9 | int16_t b = -32768; // Exactly 2 bytes |
| 10 | int32_t c = -2147483648; // Exactly 4 bytes |
| 11 | int64_t d = -9223372036854775807LL; // Exactly 8 bytes |
| 12 | |
| 13 | printf("int8_t: %d (size: %zu)\n", a, sizeof(a)); |
| 14 | printf("int16_t: %d (size: %zu)\n", b, sizeof(b)); |
| 15 | printf("int32_t: %d (size: %zu)\n", c, sizeof(c)); |
| 16 | printf("int64_t: %lld (size: %zu)\n", (long long)d, sizeof(d)); |
| 17 | |
| 18 | // Fixed-width unsigned integers |
| 19 | uint8_t ua = 255; |
| 20 | uint16_t ub = 65535; |
| 21 | uint32_t uc = 4294967295U; |
| 22 | uint64_t ud = 18446744073709551615ULL; |
| 23 | |
| 24 | printf("uint8_t: %u (size: %zu)\n", ua, sizeof(ua)); |
| 25 | printf("uint16_t: %u (size: %zu)\n", ub, sizeof(ub)); |
| 26 | printf("uint32_t: %u (size: %zu)\n", uc, sizeof(uc)); |
| 27 | printf("uint64_t: %llu (size: %zu)\n", (unsigned long long)ud, sizeof(ud)); |
| 28 | |
| 29 | // Minimum-width and fast types |
| 30 | int_least32_t least32 = 42; // At least 32 bits |
| 31 | int_fast32_t fast32 = 42; // Fastest type with at least 32 bits |
| 32 | intmax_t max_val = 9223372036854775807LL; |
| 33 | uintmax_t umax = 18446744073709551615ULL; |
| 34 | |
| 35 | printf("intmax_t size: %zu\n", sizeof(max_val)); |
| 36 | |
| 37 | // Limit macros from limits.h and stdint.h |
| 38 | printf("INT8_MIN: %d\n", INT8_MIN); |
| 39 | printf("INT8_MAX: %d\n", INT8_MAX); |
| 40 | printf("UINT8_MAX: %u\n", UINT8_MAX); |
| 41 | printf("INT16_MIN: %d\n", INT16_MIN); |
| 42 | printf("INT16_MAX: %d\n", INT16_MAX); |
| 43 | printf("UINT16_MAX: %u\n", UINT16_MAX); |
| 44 | printf("INT32_MIN: %d\n", INT32_MIN); |
| 45 | printf("INT32_MAX: %d\n", INT32_MAX); |
| 46 | printf("UINT32_MAX: %u\n", UINT32_MAX); |
| 47 | |
| 48 | // SIZE_MAX: maximum value of size_t |
| 49 | printf("SIZE_MAX: %zu\n", SIZE_MAX); |
| 50 | |
| 51 | // inttypes.h: portable format specifiers for fixed-width types |
| 52 | int64_t big = 123456789012345LL; |
| 53 | printf("int64_t: %" PRId64 "\n", big); |
| 54 | printf("uint64_t: %" PRIu64 "\n", (uint64_t)big); |
| 55 | printf("hex: %" PRIX64 "\n", (uint64_t)big); |
| 56 | |
| 57 | return 0; |
| 58 | } |
best practice
inttypes.h macros (PRId64, PRIu32, etc.) for portable printf/scanf format specifiers. Using %ld for int64_t is undefined behavior on platforms where long is 32 bits. The macros expand to the correct format string for each platform.The assert() macro checks a condition at runtime. If the condition is false, it prints a diagnostic message and calls abort(). Assertions are for catching programming errors — never use them for input validation or expected runtime conditions.
| 1 | #include <stdio.h> |
| 2 | #include <assert.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | // Precondition checking |
| 6 | void sort_array(int *arr, size_t n) { |
| 7 | assert(arr != NULL); // Precondition: array must exist |
| 8 | assert(n <= 1000000); // Precondition: reasonable size |
| 9 | |
| 10 | // Sorting logic... |
| 11 | (void)arr; |
| 12 | (void)n; |
| 13 | } |
| 14 | |
| 15 | // Invariant checking |
| 16 | struct Stack { |
| 17 | int data[100]; |
| 18 | int top; |
| 19 | }; |
| 20 | |
| 21 | void stack_push(struct Stack *s, int value) { |
| 22 | assert(s != NULL); |
| 23 | assert(s->top < 100); // Invariant: not full |
| 24 | s->data[s->top++] = value; |
| 25 | } |
| 26 | |
| 27 | int stack_pop(struct Stack *s) { |
| 28 | assert(s != NULL); |
| 29 | assert(s->top > 0); // Invariant: not empty |
| 30 | return s->data[--s->top]; |
| 31 | } |
| 32 | |
| 33 | int main(void) { |
| 34 | struct Stack s = {.top = 0}; |
| 35 | stack_push(&s, 42); |
| 36 | stack_push(&s, 99); |
| 37 | printf("Popped: %d\n", stack_pop(&s)); |
| 38 | |
| 39 | int arr[] = {5, 3, 1, 4, 2}; |
| 40 | sort_array(arr, 5); |
| 41 | |
| 42 | // In debug builds, assertions are enabled |
| 43 | // In release builds with NDEBUG, assert() does nothing: |
| 44 | // gcc -DNDEBUG -O2 -o program program.c |
| 45 | |
| 46 | printf("All assertions passed\n"); |
| 47 | return 0; |
| 48 | } |
warning
assert(validate() == 0)because validate() won't run in release. Instead, separate the side effect: int r = validate(); assert(r == 0);limits.h defines the range of integer types, while float.h defines properties of floating-point types. These headers are essential for writing portable code that works correctly regardless of the platform's data type sizes.
| 1 | #include <stdio.h> |
| 2 | #include <limits.h> |
| 3 | #include <float.h> |
| 4 | #include <stdint.h> |
| 5 | |
| 6 | int main(void) { |
| 7 | // Integer limits (from limits.h) |
| 8 | printf("=== Integer Limits ===\n"); |
| 9 | printf("CHAR_BIT: %d\n", CHAR_BIT); |
| 10 | printf("CHAR_MIN: %d\n", CHAR_MIN); |
| 11 | printf("CHAR_MAX: %d\n", CHAR_MAX); |
| 12 | printf("SCHAR_MIN: %d\n", SCHAR_MIN); |
| 13 | printf("SCHAR_MAX: %d\n", SCHAR_MAX); |
| 14 | printf("UCHAR_MAX: %u\n", UCHAR_MAX); |
| 15 | printf("SHRT_MIN: %d\n", SHRT_MIN); |
| 16 | printf("SHRT_MAX: %d\n", SHRT_MAX); |
| 17 | printf("USHRT_MAX: %u\n", USHRT_MAX); |
| 18 | printf("INT_MIN: %d\n", INT_MIN); |
| 19 | printf("INT_MAX: %d\n", INT_MAX); |
| 20 | printf("UINT_MAX: %u\n", UINT_MAX); |
| 21 | printf("LONG_MIN: %ld\n", LONG_MIN); |
| 22 | printf("LONG_MAX: %ld\n", LONG_MAX); |
| 23 | printf("ULONG_MAX: %lu\n", ULONG_MAX); |
| 24 | printf("LLONG_MIN: %lld\n", LLONG_MIN); |
| 25 | printf("LLONG_MAX: %lld\n", LLONG_MAX); |
| 26 | printf("ULLONG_MAX: %llu\n", ULLONG_MAX); |
| 27 | |
| 28 | // Platform-specific sizes |
| 29 | printf("\n=== Actual Sizes ===\n"); |
| 30 | printf("sizeof(char): %zu\n", sizeof(char)); |
| 31 | printf("sizeof(short): %zu\n", sizeof(short)); |
| 32 | printf("sizeof(int): %zu\n", sizeof(int)); |
| 33 | printf("sizeof(long): %zu\n", sizeof(long)); |
| 34 | printf("sizeof(llong): %zu\n", sizeof(long long)); |
| 35 | printf("sizeof(size_t): %zu\n", sizeof(size_t)); |
| 36 | |
| 37 | // Floating-point limits (from float.h) |
| 38 | printf("\n=== Float Limits ===\n"); |
| 39 | printf("FLT_RADIX: %d\n", FLT_RADIX); |
| 40 | printf("FLT_MANT_DIG: %d\n", FLT_MANT_DIG); |
| 41 | printf("FLT_MIN: %e\n", FLT_MIN); |
| 42 | printf("FLT_MAX: %e\n", FLT_MAX); |
| 43 | printf("FLT_EPSILON: %e\n", FLT_EPSILON); |
| 44 | printf("FLT_DIG: %d\n", FLT_DIG); |
| 45 | printf("FLT_MIN_EXP: %d\n", FLT_MIN_EXP); |
| 46 | printf("FLT_MAX_EXP: %d\n", FLT_MAX_EXP); |
| 47 | |
| 48 | printf("DBL_MANT_DIG: %d\n", DBL_MANT_DIG); |
| 49 | printf("DBL_MIN: %e\n", DBL_MIN); |
| 50 | printf("DBL_MAX: %e\n", DBL_MAX); |
| 51 | printf("DBL_EPSILON: %e\n", DBL_EPSILON); |
| 52 | printf("DBL_DIG: %d\n", DBL_DIG); |
| 53 | |
| 54 | // Practical: epsilon comparison for floating point |
| 55 | double a = 1.0 / 3.0; |
| 56 | double b = 1.0 / 3.0; |
| 57 | if (a == b) { |
| 58 | printf("a == b (exact comparison)\n"); |
| 59 | } |
| 60 | // For approximate comparison, use epsilon: |
| 61 | double diff = a - b; |
| 62 | if (diff < DBL_EPSILON && diff > -DBL_EPSILON) { |
| 63 | printf("a ~= b (epsilon comparison)\n"); |
| 64 | } |
| 65 | |
| 66 | return 0; |
| 67 | } |
note
fabs(a - b) < EPSILON instead. The appropriate epsilon depends on the magnitude of your values (relative vs. absolute comparison).