C stdlib.h Library

stdlib.h is the C standard library’s general-purpose utility header. It groups together functions that don’t fit neatly into I/O, string handling, or math: dynamic memory management, converting text to numbers, generating pseudo-random numbers, sorting and searching arrays, controlling program termination, and talking to the environment the program runs in. Almost every non-trivial C program includes it, so understanding what it offers — and where its sharp edges are — is essential.

Overview / How stdlib.h Works

stdlib.h does not define a single feature; it is a header that declares a family of unrelated but frequently needed functions and a few supporting types and macros. When you write #include <stdlib.h>, the preprocessor pastes in function prototypes (declarations, not implementations) so the compiler knows the correct argument types and return type for each function. The actual machine code for these functions lives in the C runtime library, which the linker attaches to your program automatically on virtually every platform.

The functions in stdlib.h fall into a handful of natural groups:

  • Dynamic memory managementmalloc, calloc, realloc, and free let a program request and release memory from the heap at run time, instead of being limited to fixed-size variables decided at compile time.
  • String-to-number conversionatoi, atol, atof, and the more robust strtol, strtoul, and strtod family turn text (for example, command-line arguments or file contents) into numeric values.
  • Pseudo-random numbersrand and srand generate a repeatable sequence of pseudo-random integers from a starting seed.
  • Searching and sortingqsort and bsearch work on any array of any type through function pointers, making them fully generic.
  • Arithmetic helpersabs, labs, llabs, and div/ldiv handle absolute values and combined quotient/remainder division.
  • Program and environment controlexit, abort, atexit, getenv, and system terminate the program, register cleanup callbacks, read environment variables, and run shell commands.

The most important internal concept in this header is the heap. Ordinary local variables live on the stack, a region of memory that grows and shrinks automatically as functions are called and return; their size must be known at compile time and they vanish the moment the enclosing function returns. The heap is a separate region managed manually: you ask the memory allocator (malloc and friends) for a block of a given size in bytes, and it returns a pointer to that block (or NULL if the request cannot be satisfied). That memory stays reserved until you explicitly hand it back with free, no matter which function is currently executing. This is what lets a C program build data structures whose size is only known while the program is running — an array sized by user input, a linked list that grows one node at a time, a buffer read from a file of unknown length.

Because the compiler cannot track heap memory the way it tracks stack variables, the programmer is fully responsible for its lifetime. Forgetting to call free leaks memory; calling it twice on the same pointer, or using a pointer after freeing it, causes undefined behavior. This manual bookkeeping is the single biggest source of C bugs, so a large part of mastering stdlib.h is mastering memory discipline, not just memorizing function signatures.

Syntax

#include <stdlib.h>

void   *malloc(size_t size);
void   *calloc(size_t count, size_t size);
void   *realloc(void *ptr, size_t new_size);
void    free(void *ptr);

int     atoi(const char *str);
long    strtol(const char *str, char **endptr, int base);
double  atof(const char *str);

void    srand(unsigned int seed);
int     rand(void);

void    qsort(void *base, size_t nmemb, size_t size,
              int (*compar)(const void *, const void *));
void   *bsearch(const void *key, const void *base, size_t nmemb,
                size_t size, int (*compar)(const void *, const void *));

void    exit(int status);
char   *getenv(const char *name);
Part Meaning
size_t An unsigned integer type used for sizes and counts; returned by sizeof and expected by the allocation functions.
void * A generic pointer type. Allocation functions return void * so the same function can hand back memory meant to hold any type; you assign it to a typed pointer (for example, int *) without an explicit cast in C.
compar A pointer to a comparison function used by qsort/bsearch. It receives two const void * arguments and returns negative, zero, or positive depending on their order.
endptr An out-parameter for strtol and similar functions; on return it points to the first character in the string that was not part of the number, letting you detect how much was consumed.
status The exit code passed to exit; by convention 0 (or EXIT_SUCCESS) means success and a nonzero value (or EXIT_FAILURE) signals an error to the calling shell or process.

Examples

Example 1: Dynamic Memory With malloc, realloc, and free

This example allocates an array whose size is decided at run time, fills it, then grows it with realloc to hold more elements — something a fixed-size array declared like int arr[5]; could never do.

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

int main(void) {
    int n = 5;
    int *arr = malloc((size_t)n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Allocation failed\n");
        return EXIT_FAILURE;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 10;
    }

    /* Grow the array to hold 3 more elements */
    int *tmp = realloc(arr, (size_t)(n + 3) * sizeof(int));
    if (tmp == NULL) {
        free(arr);
        fprintf(stderr, "Reallocation failed\n");
        return EXIT_FAILURE;
    }
    arr = tmp;

    for (int i = n; i < n + 3; i++) {
        arr[i] = (i + 1) * 10;
    }

    long sum = 0;
    printf("Array: ");
    for (int i = 0; i < n + 3; i++) {
        printf("%d ", arr[i]);
        sum += arr[i];
    }
    printf("\nSum of %d elements: %ld\n", n + 3, sum);

    free(arr);
    return EXIT_SUCCESS;
}

Output:

Array: 10 20 30 40 50 60 70 80 
Sum of 8 elements: 360

malloc(n * sizeof(int)) requests enough bytes for n int values and returns a pointer to the first byte; it is always checked against NULL because allocation can fail. realloc resizes an existing block, possibly moving it to a new address if it can’t grow in place — which is why the code assigns the result to a temporary pointer first and only overwrites arr after confirming success. If realloc failed and you wrote arr = realloc(arr, ...) directly, a NULL result would overwrite your only reference to the original block, leaking it. Finally, free(arr) returns the whole block to the heap in one call.

Example 2: Converting Strings To Numbers

atoi and atof are simple but give no way to detect bad input: they silently return 0 for text that isn’t a number. strtol is the safer alternative, since it reports exactly where parsing stopped and can signal overflow through errno.

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

int main(void) {
    const char *inputs[] = { "42", "  -17abc", "3.14", "not_a_number" };

    for (int i = 0; i < 4; i++) {
        char *end;
        errno = 0;
        long value = strtol(inputs[i], &end, 10);

        if (end == inputs[i]) {
            printf("\"%s\" -> not a valid number\n", inputs[i]);
        } else if (errno == ERANGE) {
            printf("\"%s\" -> out of range\n", inputs[i]);
        } else {
            printf("\"%s\" -> %ld (stopped at \"%s\")\n", inputs[i], value, end);
        }
    }

    double d = atof("3.14159");
    printf("atof(\"3.14159\") = %.5f\n", d);

    return EXIT_SUCCESS;
}

Output:

"42" -> 42 (stopped at "")
"  -17abc" -> -17 (stopped at "abc")
"3.14" -> 3 (stopped at ".14")
"not_a_number" -> not a valid number
atof("3.14159") = 3.14159

strtol skips leading whitespace, reads an optional sign, then reads digits in the given base (10 here) until a non-digit character appears. The endptr output parameter is set to point at that stopping character, so "3.14" correctly parses as the integer 3 with end pointing at ".14". When no digits can be parsed at all, end equals the original string pointer, which is how the code detects "not_a_number" as invalid. atof, by contrast, has no way to report an error — it simply returns 0.0 on invalid input, which is why it is best reserved for input you already trust.

Example 3: Generic Sorting And Searching With qsort and bsearch

qsort and bsearch work on any array, of any element type, because they take a void * base pointer, the element count, the size of one element in bytes, and a comparison function you supply. This example sorts an array of structs by score and then looks one up.

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

typedef struct {
    char name[20];
    int score;
} Student;

int compare_by_score(const void *a, const void *b) {
    const Student *sa = (const Student *)a;
    const Student *sb = (const Student *)b;
    return sa->score - sb->score;
}

int compare_key_to_score(const void *key, const void *elem) {
    int k = *(const int *)key;
    const Student *s = (const Student *)elem;
    return k - s->score;
}

int main(void) {
    Student students[] = {
        {"Alice", 88},
        {"Bob", 72},
        {"Carla", 95},
        {"Dan", 60},
        {"Eve", 81}
    };
    size_t count = sizeof(students) / sizeof(students[0]);

    qsort(students, count, sizeof(Student), compare_by_score);

    printf("Sorted by score:\n");
    for (size_t i = 0; i < count; i++) {
        printf("  %-6s %d\n", students[i].name, students[i].score);
    }

    int target = 81;
    Student *found = bsearch(&target, students, count, sizeof(Student), compare_key_to_score);
    if (found != NULL) {
        printf("Found score %d belongs to %s\n", target, found->name);
    } else {
        printf("Score %d not found\n", target);
    }

    return EXIT_SUCCESS;
}

Output:

Sorted by score:
  Dan    60
  Bob    72
  Eve    81
  Alice  88
  Carla  95
Found score 81 belongs to Eve

qsort repeatedly calls compare_by_score on pairs of elements, treating a negative return as “first comes before second,” zero as “equal,” and positive as “first comes after second.” Because it only knows each element’s size in bytes, not its type, the comparison function must cast the const void * arguments back to const Student * before reading fields. bsearch uses the same idea but requires the array to already be sorted according to the comparison function it is given, since it performs a binary search — repeatedly halving the search range — rather than a linear scan; running it on unsorted data produces undefined results.

Example 4: Pseudo-Random Numbers And Arithmetic Helpers

rand generates pseudo-random integers from an internal, deterministic algorithm seeded by srand. This example also shows abs and div, two small but handy arithmetic utilities.

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

int main(void) {
    srand(42);

    int values[5];
    int all_in_range = 1;

    for (int i = 0; i < 5; i++) {
        values[i] = rand() % 100 + 1;
        if (values[i] < 1 || values[i] > 100) {
            all_in_range = 0;
        }
    }

    printf("Generated 5 numbers between 1 and 100.\n");
    printf("All in valid range: %s\n", all_in_range ? "yes" : "no");

    int a = -15;
    printf("abs(%d) = %d\n", a, abs(a));

    div_t result = div(17, 5);
    printf("17 / 5 -> quotient = %d, remainder = %d\n", result.quot, result.rem);

    return EXIT_SUCCESS;
}

Output:

Generated 5 numbers between 1 and 100.
All in valid range: yes
abs(-15) = 15
17 / 5 -> quotient = 3, remainder = 2

rand() returns an integer between 0 and RAND_MAX; the common idiom rand() % 100 + 1 maps that into the range 1–100 (though for uniform distributions in demanding applications, this modulo trick has known bias and better techniques exist). The exact numbers rand() produces for a given seed are implementation-defined, so this example checks only that the values fall in range rather than printing them directly — a program that hardcodes expected rand() values is not portable. div(17, 5) returns a div_t struct with both the quotient and remainder computed together, which can be more efficient than computing 17 / 5 and 17 % 5 separately since both operations often share the same underlying CPU instruction.

How It Works Step By Step

For a heap allocation such as malloc(40), the following happens internally:

  • The program asks the C runtime’s memory allocator for 40 bytes.
  • The allocator searches its internal free list — a data structure tracking unused chunks of heap memory — for a chunk large enough to satisfy the request, possibly requesting more memory from the operating system (via brk/mmap on Unix-like systems) if nothing is available.
  • It marks that chunk as in-use, records its size in hidden bookkeeping bytes placed just before the returned address, and hands back a pointer to the usable portion.
  • The program uses the memory through that pointer for as long as it needs it.
  • When free(ptr) is called, the allocator reads the hidden size information, marks the chunk as unused again, and may merge it with adjacent free chunks to reduce fragmentation, making it available for future allocations.

For qsort, the process is similarly mechanical: the algorithm (typically some variant of quicksort or introsort) treats the array purely as a sequence of fixed-size, opaque byte blocks. It never looks inside an element; it only swaps whole blocks of size bytes and asks your comparison function which of two blocks should come first. This is exactly what makes it work for int arrays, struct arrays, or anything else — the type-specific knowledge lives entirely in the comparison function you write, not in qsort itself.

Common Mistakes

Mistake 1: Not checking the result of malloc/realloc.

int *arr = malloc(1000000000000UL * sizeof(int));
arr[0] = 5; /* crashes if malloc failed */

On an allocation failure, malloc returns NULL instead of throwing an exception. Dereferencing a NULL pointer causes a crash (or worse, undefined behavior). Always check:

int *arr = malloc(1000000000000UL * sizeof(int));
if (arr == NULL) {
    fprintf(stderr, "Out of memory\n");
    exit(EXIT_FAILURE);
}
arr[0] = 5;

Mistake 2: Using memory after freeing it (a “use-after-free” bug), or freeing it twice.

free(arr);
printf("%d\n", arr[0]); /* undefined behavior: arr was freed */
free(arr);            /* undefined behavior: double free */

Once memory is freed, the pointer becomes a “dangling pointer” — it still holds the old address, but that memory may already be reused for something else. Reading or writing through it, or freeing it again, corrupts the heap in ways that often don’t crash immediately, making the bug hard to trace. Set the pointer to NULL immediately after freeing so accidental reuse is easy to catch:

free(arr);
arr = NULL;

Mistake 3: Ignoring the difference between atoi and strtol on invalid input.

int count = atoi(user_input); /* silently 0 if user_input isn't a number */
for (int i = 0; i < count; i++) { /* may just never run, with no warning */ }

atoi cannot distinguish between the text "0" and the text "banana" — both return 0. When the input might be invalid (which is almost always true for user or file input), use strtol and check its endptr as shown in Example 2.

Best Practices

  • Always check the return value of malloc, calloc, and realloc for NULL before using the memory.
  • Match every successful allocation with exactly one free; use a tool like valgrind during development to catch leaks and misuse.
  • Assign realloc‘s result to a temporary pointer, not directly back to the original variable, so a failed resize doesn’t lose your only reference to the existing block.
  • Set pointers to NULL immediately after freeing them to make accidental reuse easier to detect.
  • Prefer strtol/strtod over atoi/atof whenever the input could be malformed, since only the former can report errors.
  • Seed rand with srand exactly once near the start of main (commonly with a time-based seed for varying results, or a fixed seed for reproducible tests) — never inside a loop that calls rand repeatedly.
  • Write comparison functions for qsort/bsearch carefully: for integer fields, prefer explicit comparisons ((a > b) - (a < b)) over subtraction when the values could overflow a signed int.
  • Use EXIT_SUCCESS and EXIT_FAILURE instead of raw 0/1 for portability and readability when calling exit or returning from main.

Practice Exercises

  • Exercise 1: Write a program that uses malloc to allocate an array of 10 double values, fills it with the squares of 1 through 10, prints them, and then frees the array. (Expected first and last lines of output: 1.00 and 100.00.)
  • Exercise 2: Write a function int safe_parse(const char *s, int *out) that uses strtol to attempt parsing s as an integer, storing the result in *out and returning 1 on success or 0 if s contains anything other than a valid integer (including trailing characters). Test it with "123", "123x", and "abc".
  • Exercise 3: Given an array of 8 integers, use qsort to sort it in descending order (hint: reverse the sign of the comparison function’s return value), then use bsearch to check whether the value 42 is present. Print whether it was found.

Summary

  • stdlib.h bundles memory management, number conversion, random numbers, sorting/searching, arithmetic helpers, and program/environment control.
  • malloc/calloc/realloc/free manage heap memory manually; every allocation must be checked for NULL and eventually freed exactly once.
  • strtol and friends are safer than atoi/atof because they can report where parsing stopped and whether it failed.
  • rand/srand produce a deterministic pseudo-random sequence from a seed; the exact values are implementation-defined, so don’t rely on specific outputs.
  • qsort and bsearch are generic over any type through void * and a programmer-supplied comparison function.
  • exit, abort, getenv, and system let a program terminate cleanly, terminate abnormally, read environment variables, and invoke external commands.