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 management —
malloc,calloc,realloc, andfreelet 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 conversion —
atoi,atol,atof, and the more robuststrtol,strtoul, andstrtodfamily turn text (for example, command-line arguments or file contents) into numeric values. - Pseudo-random numbers —
randandsrandgenerate a repeatable sequence of pseudo-random integers from a starting seed. - Searching and sorting —
qsortandbsearchwork on any array of any type through function pointers, making them fully generic. - Arithmetic helpers —
abs,labs,llabs, anddiv/ldivhandle absolute values and combined quotient/remainder division. - Program and environment control —
exit,abort,atexit,getenv, andsystemterminate 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/mmapon 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, andreallocforNULLbefore using the memory. - Match every successful allocation with exactly one
free; use a tool likevalgrindduring 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
NULLimmediately after freeing them to make accidental reuse easier to detect. - Prefer
strtol/strtodoveratoi/atofwhenever the input could be malformed, since only the former can report errors. - Seed
randwithsrandexactly once near the start ofmain(commonly with a time-based seed for varying results, or a fixed seed for reproducible tests) — never inside a loop that callsrandrepeatedly. - Write comparison functions for
qsort/bsearchcarefully: for integer fields, prefer explicit comparisons ((a > b) - (a < b)) over subtraction when the values could overflow a signedint. - Use
EXIT_SUCCESSandEXIT_FAILUREinstead of raw0/1for portability and readability when callingexitor returning frommain.
Practice Exercises
- Exercise 1: Write a program that uses
mallocto allocate an array of 10doublevalues, fills it with the squares of 1 through 10, prints them, and then frees the array. (Expected first and last lines of output:1.00and100.00.) - Exercise 2: Write a function
int safe_parse(const char *s, int *out)that usesstrtolto attempt parsingsas an integer, storing the result in*outand returning1on success or0ifscontains 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
qsortto sort it in descending order (hint: reverse the sign of the comparison function’s return value), then usebsearchto check whether the value42is present. Print whether it was found.
Summary
stdlib.hbundles memory management, number conversion, random numbers, sorting/searching, arithmetic helpers, and program/environment control.malloc/calloc/realloc/freemanage heap memory manually; every allocation must be checked forNULLand eventually freed exactly once.strtoland friends are safer thanatoi/atofbecause they can report where parsing stopped and whether it failed.rand/srandproduce a deterministic pseudo-random sequence from a seed; the exact values are implementation-defined, so don’t rely on specific outputs.qsortandbsearchare generic over any type throughvoid *and a programmer-supplied comparison function.exit,abort,getenv, andsystemlet a program terminate cleanly, terminate abnormally, read environment variables, and invoke external commands.
