Binary Search in C Using the bsearch Function
The bsearch function, defined in the standard
<stdlib.h> header, provides a generic implementation
of the binary search algorithm in the C programming language. It
efficiently locates a target item within a contiguous, pre-sorted memory
buffer in logarithmic time (\(O(\log
n)\)). This article breaks down how bsearch works
under the hood, how it handles generic memory addresses using pointer
arithmetic, and how it interacts with user-defined comparison
callbacks.
Function Signature and Parameters
The standard signature for bsearch is:
void *bsearch(const void *key,
const void *base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *));The function accepts five arguments:
key: A pointer to the element you want to find.base: A pointer to the first byte of the sorted array in memory.nmemb: The total count of elements in the array.size: The size in bytes of an individual element (sizeof(...)).compar: A pointer to a comparison function that dictates the ordering logic.
Memory Navigation with Generic Pointers
Because bsearch operates on void *
pointers, the compiler does not know the concrete data type stored in
the buffer. To inspect individual elements, bsearch uses
byte-level pointer arithmetic using the size parameter.
To calculate the memory address of an element at index
mid, the function internally offsets from the base pointer
using byte casting:
\[\text{element\_address} = (\text{const char *})\text{base} + (\text{mid} \times \text{size})\]
Casting base to const char * ensures that
addition advances memory one byte at a time. Multiplying the index by
size advances the pointer by the exact byte length of each
element, allowing bsearch to work seamlessly with native
types, structs, or custom data structures.
The Search Execution Loop
Once initialized, bsearch executes the classic binary
search algorithm across the indices:
Calculate Midpoint: The search boundary begins with indices
low = 0andhigh = nmemb - 1. During each iteration, the midpoint index is calculated:mid = low + (high - low) / 2.Invoke Comparator: The function computes the address of the element at
midand passeskeyand the middle element to thecomparfunction:int result = compar(key, (const char *)base + (mid * size));Adjust Search Space:
- If
result < 0: The targetkeyis smaller than the middle element. The search interval shrinks to the left half by settinghigh = mid - 1. - If
result > 0: The targetkeyis larger than the middle element. The search interval shrinks to the right half by settinglow = mid + 1. - If
result == 0: A match is found.bsearchimmediately returns a generic pointer (void *) to the matching element.
- If
Termination: If
lowexceedshighwithout a match, the loop terminates and the function returnsNULL.
The Comparator Contract
The caller must supply a comparison function that mirrors the sorting
order of the array. The comparator must accept two
const void * arguments: the first argument is always the
key pointer passed into bsearch, and the
second argument is a pointer to the current array element being
evaluated.
The callback must return:
- An integer less than
0ifkeyprecedes the array element. - An integer greater than
0ifkeyfollows the array element. - Exactly
0if both elements are equal according to the search criteria.
Usage Constraints and Behavior
- Strict Pre-Sorting: The memory buffer must already
be sorted in ascending order relative to the provided comparator. If the
buffer is unsorted, the behavior of
bsearchis undefined, and it may returnNULLeven if the element exists. - Handling Duplicates: If the memory buffer contains
multiple elements that match
key(where the comparator returns0), the C standard does not specify which duplicate element will be returned. - Type Safety: Because
bsearchreturns avoid *, the calling code must cast the return value back to the appropriate data type before dereferencing it.