strtol(3)
C library function
Converts the start of a string to a long in the base you choose, tells you where parsing stopped through endptr and reports overflow with errno.
#include <stdlib.h>
long strtol(const char *restrict nptr, char **restrict endptr, int base);Description
strtol reads an integer at the start of nptr. It skips whitespace, accepts a sign, then reads as many valid digits as it can in base base, from 2 to 36: past 9, the letters a to z (or A to Z) stand for 10 to 35. In base 16, a 0x prefix is accepted. With base set to 0, the prefix decides: 0x for hexadecimal, a leading 0 for octal, decimal otherwise.
If endptr is not NULL, strtol stores there the address of the first character it did not use. When no digit could be read, it stores nptr itself. That is the whole difference from atoi(3).
Return value
The converted integer, or 0 if no conversion took place. If the value is beyond what a long can hold, strtol returns LONG_MAX or LONG_MIN and sets errno to ERANGE. On success, errno is left unchanged.
Pitfalls
0, LONG_MAX and LONG_MIN are also valid results. To know whether an overflow happened, set errno to 0 right before the call, then test it right after. Without that reset, you may be reading the error of an earlier call.
A complete conversion takes three checks: end == nptr (no digits), errno == ERANGE (out of range), *end != '\0' (characters left after the number). The example runs them in that order.
The result is a long. To store it in an int, first check that it lies between INT_MIN and INT_MAX: that is the fits_in_int exercise.
In base 0, "010" is 8, not 10, because the leading zero announces octal. If you read numbers typed by a human, pass 10 explicitly.
Since C23, bases 0 and 2 also accept the 0b prefix. glibc supports it since version 2.38, when the program is compiled as C23: the same call then gives a different result depending on -std. musl, the C library of the Grademe exercise environment, returns 0 for "0b101" even in C23. Do not count on this prefix if your code has to run elsewhere.
Leading whitespace is accepted without a word. If you want to reject it, test the first character with isspace before the call.
Example
#include <errno.h>#include <stdio.h>#include <stdlib.h>
int main(int argc, char **argv){ for (int i = 1; i < argc; i++) { char *end; long v;
errno = 0; v = strtol(argv[i], &end, 10); if (end == argv[i]) printf("\"%s\": no digits\n", argv[i]); else if (errno == ERANGE) printf("\"%s\": out of range, strtol returns %ld\n", argv[i], v); else if (*end != '\0') printf("\"%s\": %ld, then \"%s\" left unread\n", argv[i], v, end); else printf("\"%s\": %ld\n", argv[i], v); } return 0;}$ gcc -Wall -Wextra parse_number.c -o parse_number$ ./parse_number 42 " -17abc" abc 99999999999999999999"42": 42" -17abc": -17, then "abc" left unread"abc": no digits"99999999999999999999": out of range, strtol returns 9223372036854775807And the effect of base 0, compiled as C23 (the default mode of GCC 15), then as C17:
#include <stdio.h>#include <stdlib.h>
int main(void){ const char *inputs[] = {"010", "0x1A", "0b101"};
for (int i = 0; i < 3; i++) { char *end; long v = strtol(inputs[i], &end, 0);
printf("\"%s\" in base 0: %ld, rest \"%s\"\n", inputs[i], v, end); } return 0;}$ gcc -Wall -Wextra bases.c -o bases$ ./bases"010" in base 0: 8, rest """0x1A" in base 0: 26, rest """0b101" in base 0: 5, rest ""$ gcc -Wall -Wextra -std=gnu17 bases.c -o bases17$ ./bases17"010" in base 0: 8, rest """0x1A" in base 0: 26, rest """0b101" in base 0: 0, rest "b101"In C17, 0b101 is read as the number 0 followed by text that strtol could not convert: end points at the b.
strtol
Write gm_strtol, which reads a long in the given base, detects the base from the prefix when it is 0, and points *endptr at the first unconverted character.