๐Ÿ“š 24. C Standard Library Essentials

Use the standard library for common tasks such as converting text, sorting values, copying bytes, and reading files. Each example below shows the required header, a complete program, and the result to check.

Learning goal: Choose the right header, understand the functionโ€™s inputs and result, and check the conditions it requires before using it.

24.1 The C Standard Library Map

A header supplies the declarations, types, and macros for a group of facilities. Include the header that specifies the function you use; knowing its name is not enough. Library function definitions are supplied by the C implementation.

Important points:

  1. stdio.h โ€” streams and formatted I/O; stdlib.h โ€” allocation, conversion, sorting and utilities; string.h โ€” byte strings; ctype.h โ€” character classification; math.h โ€” mathematics; time.h โ€” dates and clocks; errno.h โ€” error codes; assert.h โ€” checks; limits.h, stdint.h and inttypes.h โ€” integer properties.
Remember: Portable C programs include the header that declares every library function they use.

24.2 Math Functions with math.h

Use sqrt for a square root, fabs for floating-point absolute value, and floor or ceil for downward or upward rounding. The unsuffixed versions shown use double. Versions such as sqrtf and sqrtl work with float and long double. Some toolchains also need -lm when linking.

Complete C program:

#include <stdio.h>
#include <math.h>

int main(void)
{
    printf("sqrt(81) = %.1f\n", sqrt(81.0));
    printf("ceil(2.3) = %.1f\n", ceil(2.3));
    printf("fabs(-5.5) = %.1f\n", fabs(-5.5));
    return 0;
}

Expected output:

sqrt(81) = 9.0
ceil(2.3) = 3.0
fabs(-5.5) = 5.5

Step-by-step explanation:

  1. sqrt finds the nonnegative square root.
  2. ceil(2.3) gives the floating-point value 3.0; it does not return an int. floor(2.3) would give 2.0.
  3. fabs computes the absolute magnitude of a floating value.
Remember: On common Unix-like GCC toolchains, build with gcc -std=c17 demo.c -lm -o demo. Read each functionโ€™s valid input range; for example, sqrt requires a non-negative real input for a real result.

24.3 Character Classification with ctype.h

Classify one byte value at a time with functions such as isdigit or isspace. If a value comes from a char variable, cast it to unsigned char before passing it. An int returned by fgetc must retain its EOF check before being treated as a character.

Complete C program:

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char text[] = "C Lab 7";
    for (int i = 0; text[i] != '\0'; ++i)
        if (isdigit((unsigned char)text[i]))
            printf("Digit: %c\n", text[i]);
    return 0;
}

Expected output:

Digit: 7

Step-by-step explanation:

  1. The loop visits each character until the null terminator.
  2. isdigit tests each byte.
  3. Only the digit is printed.
Remember: These are byte-character functions, not complete Unicode text processors. Many classifications depend on the active locale; do not assume every character of a multi-byte text encoding fits in one char.

24.4 Conversions with strtol and strtod

strtol converts a numeric prefix to long, while strtod converts one to double. Both supply an end pointer showing where they stopped. Use that pointer and the documented range-error checks to decide whether the whole input is acceptable.

Complete C program:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *end;
    double amount = strtod("12.50kg", &end);
    printf("Amount = %.2f\n", amount);
    printf("Unit = %s\n", end);
    return 0;
}

Expected output:

Amount = 12.50
Unit = kg

Step-by-step explanation:

  1. strtod converts the initial numeric portion of the string.
  2. end points to the unconverted suffix kg.
  3. This demonstration deliberately keeps the suffix kg. A parser that expects only a number should also reject an unwanted suffix, detect no conversion, and check the documented range errors.
Remember: Do not use atof when you need reliable invalid-input reporting.

24.5 Sorting Arrays with qsort

qsort sorts an array using a comparison function you supply. Give it the array address, element count, and size of one element. The comparison resultโ€™s sign indicates less, equal, or greater. Equal elements are not guaranteed to keep their original relative order.

Complete C program:

#include <stdio.h>
#include <stdlib.h>

int compareInts(const void *pa, const void *pb)
{
    int a = *(const int *)pa, b = *(const int *)pb;
    return (a > b) - (a < b);
}

int main(void)
{
    int nums[] = {9, 2, 7, 1};
    qsort(nums, 4, sizeof nums[0], compareInts);
    for (size_t i = 0; i < 4; ++i) printf("%d ", nums[i]);
    putchar('\n');
    return 0;
}

Expected output:

1 2 7 9 

Step-by-step explanation:

  1. qsort invokes compareInts to compare elements.
  2. The comparator avoids subtraction, which could overflow.
  3. The expression (a > b) - (a < b) produces -1, 0, or 1 without the overflow that a - b can cause for large opposite-signed values.
Remember: Do not depend on how many comparisons occur or which pair is compared first. The C interface does not require the quicksort algorithm or guarantee a stable sort.

24.6 Searching a Sorted Array with bsearch

Before calling bsearch, sort the elements into the order used by its comparison function. It returns a pointer to a match or NULL. Searching unsorted data with it does not give a reliable answer.

Complete C program:

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b)
{
    int x = *(const int *)a, y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void)
{
    int sorted[] = {2, 4, 6, 8, 10};
    int target = 8;
    int *found = bsearch(&target, sorted, 5, sizeof sorted[0], cmp);
    printf("Found = %s\n", found ? "yes" : "no");
    return 0;
}

Expected output:

Found = yes

Step-by-step explanation:

  1. The sorted array satisfies the precondition of bsearch.
  2. The search uses cmp to establish the keyโ€™s order relative to elements.
  3. A non-NULL pointer indicates a match.
Remember: If several elements match, bsearch does not specify which matching element is returned.

24.7 Assertions and Defensive Programming

assert checks an assumption when assertions are enabled. Defining NDEBUG before including assert.h disables those checks. A user entering bad data is an expected input case, so handle it explicitly rather than relying on assert.

Complete C program:

#include <stdio.h>
#include <assert.h>

int squareNonnegative(int n)
{
    assert(n >= 0);
    return n * n;
}

int main(void)
{
    printf("Square = %d\n", squareNonnegative(5));
    return 0;
}

Expected output:

Square = 25

Step-by-step explanation:

  1. The assert condition is true for n = 5.
  2. The function computes and returns 25.
  3. A false assertion normally terminates the program with a diagnostic when assertions are enabled.
Remember: Do not put required work such as reading input or incrementing a counter inside assert: disabling assertions removes that evaluation. The exampleโ€™s multiplication also assumes n is small enough not to overflow.

24.8 Error Reporting with errno and perror

First check the functionโ€™s documented success or failure result. Inspect errno only when the function documents how it uses that value. For conversions such as strtol, clear errno before the call so an earlier error is not mistaken for a new one.

Complete C program:

#include <stdio.h>

int main(void)
{
    FILE *fp = fopen("missing_codebhavya_example_48301.txt", "r");
    if (fp == NULL) {
        puts("File could not be opened");
        return 0;
    }
    fclose(fp);
    return 0;
}

Expected output:

File could not be opened

Step-by-step explanation:

  1. The file name is intended as an illustrative missing file.
  2. fopen returns NULL if opening fails.
  3. A real application can call perror("fopen") before returning an error status.
Remember: An error message and errno are meaningful only when the called function documents that it signals errors that way.

24.9 Random Numbers with rand โ€” and Limitations

rand generates repeatable pseudorandom values; srand chooses the starting seed. It is suitable for a simple classroom simulation, but not for passwords or secret tokens.

Complete C program:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    srand(123U);
    int dice = rand() % 6 + 1;
    printf("Dice between 1 and 6: %s\n",
           dice >= 1 && dice <= 6 ? "yes" : "no");
    return 0;
}

Expected output:

Dice between 1 and 6: yes

Step-by-step explanation:

  1. srand(123) selects a deterministic seed for a given implementation.
  2. rand generates a pseudorandom integer.
  3. The modulo expression produces a value between 1 and 6.
Remember: The same seed reproduces a sequence for a given implementation, not one universal sequence across compilers. rand() % 6 maps results to a die range, but those outcomes need not be exactly equally likely.

24.10 Date and Time Basics with time.h

time obtains calendar time, and difftime compares two calendar-time values in seconds. clock reports processor time used by the program, rather than a portable stopwatch for elapsed real time. Check each functionโ€™s documented failure result.

Complete C program:

#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t now = time(NULL);
    if (now == (time_t)-1) return 1;
    double seconds = difftime(now, now);
    printf("Difference = %.0f seconds\n", seconds);
    return 0;
}

Expected output:

Difference = 0 seconds

Step-by-step explanation:

  1. time(NULL) requests the current calendar time.
  2. The code checks the documented failure representation.
  3. difftime applied to equal times gives zero.
Remember: Do not assume time_t is always an integer count of seconds since 1970 on every implementation.

24.11 Memory and Byte Operations

memcpy and memmove copy a specified number of bytes. Use memmove when source and destination overlap. memset fills bytes with a selected byte value. None of these functions adds a string terminator for you.

Complete C program:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char text[16] = "ABCDE";
    memmove(text + 2, text, 3);
    puts(text);
    return 0;
}

Expected output:

ABABC

Step-by-step explanation:

  1. The source contains A, B and C.
  2. memmove copies those three bytes to positions 2, 3 and 4, even though source and destination overlap.
  3. The original terminating null character at position 5 remains.
Remember: Make sure both ranges fit their objects before copying. memcpy must not be used for overlap. Also, memset writes byte patterns; all-zero bytes are not a portable initializer for every possible pointer or floating-point type.

24.12 Safe C Coding Checklist

Before reusing a library example, check its input assumptions, array capacities, valid object lifetimes, and error paths. Passing the right arguments matters as much as choosing the right function.

Important points:

  1. Check input results and handle invalid records.
  2. Keep array indices inside their bounds; allow space for null terminators.
  3. Check allocation success and arithmetic overflow before allocating.
  4. Never dereference NULL, dangling or uninitialized pointers.
  5. Use format specifiers matching the actual argument types.
  6. Avoid signed integer overflow and invalid shifts.
  7. Close files and free allocated memory on every relevant exit path.
  8. Compile with warnings enabled and test edge cases.
Remember: Memory safety and undefined behavior are cross-cutting topics. Refer back to them while studying arrays, strings, pointers, files and dynamic memory.

24.13 Quick Revision

Library essentials

math.h, ctype.h, string.h, stdlib.h, time.h, errno.h and assert.h.

Safe practices

Check return values, bounds, types, conversions and lifetime.

24.14 Quick MCQs

  1. Which function searches an already sorted array? bsearch().
  2. What is the important difference between memcpy and memmove? memmove permits overlapping regions.
  3. Which header declares isdigit()? ctype.h.
  4. Should rand() create passwords? No.
  5. What should you check after calling malloc()? The returned pointer is not NULL.

24.15 Practice Problems

  1. Read a line and safely convert it to an integer with strtol.
  2. Sort five numbers using qsort and a correct comparator.
  3. Use strchr and strstr to search a string.
  4. Count letters and digits using ctype.h.
  5. Write a program that uses perror to explain why a file failed to open.

24.16 Key Takeaway

Good C code = correct algorithms + correct library use + checked inputs + safe memory access.

โ† Back to C Programming topics

โ† Previous Topic: Advanced C PreprocessorNext Topic: C Practice โ†’