The stdlib.h toolbox

stdlib.h holds the functions that do not belong to input, strings or maths: number parsing, sorting, searching, program exit and memory. This lesson collects the four the course has not used yet. Sorting and searching with callbacks were introduced in lessons 29 and 49; here they are the standard library versions.

strtol and strtoul

strtol converts text to a long and reports where it stopped through the end pointer. The base is explicit, so 0 means automatic (0x for hex, 0 for octal), 10 decimal and 16 hexadecimal. A value out of range sets errno to ERANGE, which the program checks after resetting it to zero.

#include <errno.h> #include <stdio.h> #include <stdlib.h> int main(void) { char *end = NULL; errno = 0; long decimal = strtol("123abc", &end, 10); printf("decimal: %ld, rest: %s\n", decimal, end); errno = 0; long hex = strtol("0x1F", &end, 16); printf("hex: %ld\n", hex); errno = 0; long big = strtol("999999999999999999999", &end, 10); printf("big: %ld, errno: %d\n", big, errno); return 0; }

gcc -Wall -Wextra parse.c -o parse
./parse

decimal: 123, rest: abc
hex: 31
big: 9223372036854775807, errno: 34

The first call stops at the letter and leaves end pointing at it, which is how a parser finds the invalid tail. The last call clamps to LONG_MAX and reports ERANGE: 34 is the value of that macro on this system, and the returned number is a valid long, not a failure sentinel.

qsort

qsort sorts any array in place. It takes the base address, the element count, the element size and a comparator that receives two const void * pointers. The comparator returns a negative number, zero or a positive number; the (a > b) - (a < b) form avoids the overflow of a plain subtraction, which the last section shows.

#include <stdio.h> #include <stdlib.h> static int compare(const void *left, const void *right) { int a = *(const int *) left; int b = *(const int *) right; return (a > b) - (a < b); } int main(void) { int values[6] = {42, 7, 19, 3, 88, 1}; qsort(values, 6, sizeof values[0], compare); for (int i = 0; i < 6; i++) { printf("%d ", values[i]); } printf("\n"); return 0; }

gcc -Wall -Wextra sort.c -o sort
./sort

1 3 7 19 42 88

The library chooses the algorithm; the standard does not promise which one, only the result and the complexity. The comparator receives addresses into the array, so the casts back to const int * are the price of a function that works for any element type.

bsearch

bsearch is the binary search of lesson 49 with the same interface as qsort, which is why the same comparator works for both. The array must be sorted by that comparator. The result is a pointer to the matching element, or NULL.

#include <stdio.h> #include <stdlib.h> static int compare(const void *left, const void *right) { int a = *(const int *) left; int b = *(const int *) right; return (a > b) - (a < b); } int main(void) { int values[6] = {1, 3, 7, 19, 42, 88}; int key = 19; int *found = bsearch(&key, values, 6, sizeof values[0], compare); if (found != NULL) { printf("found at index %td\n", found - values); } else { printf("not found\n"); } return 0; }

gcc -Wall -Wextra search.c -o search
./search

found at index 3

The subtraction found - values gives the index in elements, printed with %td as lesson 28 showed. The key is passed by address because the library does not know its type; the comparator reads it through the same cast as the elements.

exit and atexit

exit ends the program from any function and runs the functions registered with atexit, in reverse order of registration. Returning from main is the same as calling exit with the returned value. abort is the opposite: it ends the program immediately and runs no handlers.

#include <stdio.h> #include <stdlib.h> static void goodbye(void) { printf("goodbye\n"); } int main(void) { if (atexit(goodbye) != 0) { return EXIT_FAILURE; } printf("working\n"); exit(EXIT_SUCCESS); }

gcc -Wall -Wextra exit.c -o exit
./exit; echo "status: $?"

working
goodbye
status: 0

The handler runs after the last line of the program, not before it. An atexit handler is the place for flushing a log or removing a temporary file; a program that fails a test usually calls abort, which skips the handler on purpose.

A subtraction comparator, atoi

The shortest comparator is return a - b;, and it is wrong for values whose difference does not fit in an int. With INT_MAX and INT_MIN the subtraction overflows, UBSan reports it, and the comparator returns a value whose sign may be wrong:

#include <stdio.h> #include <stdlib.h> static int compare(const void *left, const void *right) { int a = *(const int *) left; int b = *(const int *) right; return a - b; } int main(void) { int values[2] = {2147483647, -2147483648}; qsort(values, 2, sizeof values[0], compare); printf("%d %d\n", values[0], values[1]); return 0; }

gcc -fsanitize=undefined -g -Wall -Wextra overflow.c -o overflow
./overflow; echo "status: $?"

overflow.c:9:14: runtime error: signed integer overflow: 2147483647 - -2147483648 cannot be represented in type 'int'
2147483647 -2147483648
exit: 0

The order happens to be unchanged, but the comparator's sign is not reliable, and a larger array would sort incorrectly. The (a > b) - (a < b) form returns exactly -1, 0 or 1 without arithmetic on the values themselves.

The second trap is atoi, which has no way to report an error. It returns zero for text that is not a number and for the number zero, and it cannot tell the two apart:

#include <stdio.h> #include <stdlib.h> int main(void) { int value = atoi("abc"); printf("atoi: %d\n", value); return 0; }

gcc -Wall -Wextra atoi.c -o atoi
./atoi

atoi: 0

A program that reads numbers from a file should use strtol from the first section, check the end pointer, and treat "no digits consumed" as an error. atoi is fine only for constants known to be valid.

Exercises

  1. Parse a list of numbers separated by commas with strtol, advancing the end pointer after each one.
  2. Sort an array of strings with qsort and a comparator built on strcmp.
  3. Search the same string array with bsearch and print the index of a word that is present and of one that is not.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does the end pointer from strtol report?

    Answer

    Where the number stopped in the text

    The pointer is left at the first character that was not part of the number, which is how the caller finds the tail.

  2. What must a qsort comparator return?

    Answer

    A negative, zero or positive number

    Only the sign matters, and the safe form (a > b) - (a < b) avoids arithmetic overflow on the values.

  3. What must be true before bsearch is called?

    Answer

    The array is sorted by the same comparator

    bsearch uses the same ordering as the sort, so an unsorted array can hide the key in the discarded half.

  4. What does atexit guarantee about the handler?

    Answer

    It runs when exit is called, in reverse order

    exit runs the registered handlers in reverse registration order, while abort skips them.

  5. What does this program print for 123abc? #include <stdio.h> #include <stdlib.h> int main(void){char *e=NULL;long d=strtol("123abc",&e,10);printf("decimal: %ld, rest: %s\n",d,e);return 0;}

    Answer

    decimal: 123, rest: abc

    The conversion stops at the letter, and the end pointer prints the remaining text.

  6. What does the large-number strtol call report?

    Answer

    9223372036854775807 and errno 34

    The value is clamped to LONG_MAX, and the out-of-range condition sets ERANGE, which is 34 here.

  7. What does this program print? #include <stdio.h> #include <stdlib.h> static 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 v[6]={42,7,19,3,88,1};qsort(v,6,sizeof v[0],cmp);for(int i=0;i<6;i++)printf("%d ",v[i]);printf("\n");return 0;}

    Answer

    1 3 7 19 42 88

    The comparator orders the integers ascending, and the loop prints the sorted array.

  8. What does this program print for the key 19? #include <stdio.h> #include <stdlib.h> static 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 v[6]={1,3,7,19,42,88},k=19;int*f=bsearch(&k,v,6,sizeof v[0],cmp);printf("%td\n",f-v);return 0;}

    Answer

    3

    19 is the fourth element, and the pointer subtraction counts elements from the base.

  9. This program is run with ./exit; echo "status: $?". What does the shell print? #include <stdio.h> #include <stdlib.h> static void bye(void){printf("goodbye\n");} int main(void){atexit(bye);printf("working\n");exit(EXIT_SUCCESS);}

    Answer

    working / goodbye / status: 0

    The handler runs after the last line, and the exit status is zero.

  10. Why is return a - b a risky comparator?

    Answer

    The subtraction can overflow for extreme values

    The difference of the largest and smallest int does not fit, and the wrong sign can misorder the elements.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 with the UndefinedBehaviorSanitizer runtime on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev