Converting a string to an integer is one of the most common operations in C programs that read user input, parse config files, or process command-line arguments. atoi is the obvious function to reach for, but it silently fails in ways that cause real bugs. Here is why it is dangerous and what the complete alternative looks like.
What atoi Does Wrong
#include <stdio.h>
#include <stdlib.h>
int main() {
/* All of these return 0 from atoi — identical result for different reasons */
printf("%dn", atoi("0")); /* 0 — correct */
printf("%dn", atoi("abc")); /* 0 — invalid input, silently! */
printf("%dn", atoi("")); /* 0 — empty string, silently! */
printf("%dn", atoi("0abc")); /* 0 — stops at 'a', silently! */
printf("%dn", atoi("99999999999999")); /* undefined behavior on overflow! */
return 0;
}Three problems:
- Returns 0 for both the string “0” and any invalid input — indistinguishable
- Stops at the first non-digit without signaling that it did
- Overflow is undefined behavior — no detection, no error
strtol — The Correct Alternative
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
/* Returns 1 on success (stores value in *out), 0 on any error */
int parse_int(const char *str, int *out) {
char *endptr;
errno = 0;
long value = strtol(str, &endptr, 10);
/* Error 1: no digits were parsed */
if (endptr == str) {
fprintf(stderr, "Not a number: '%s'n", str);
return 0;
}
/* Error 2: trailing non-numeric characters */
if (*endptr != '') {
fprintf(stderr, "Trailing garbage in '%s': '%s'n", str, endptr);
return 0;
}
/* Error 3: overflow or underflow */
if (errno == ERANGE) {
fprintf(stderr, "Overflow: '%s'n", str);
return 0;
}
/* Error 4: value out of int range (even though it fits in long) */
if (value < INT_MIN || value > INT_MAX) {
fprintf(stderr, "Out of int range: %ldn", value);
return 0;
}
*out = (int)value;
return 1;
}
int main() {
int n;
if (parse_int("42", &n)) printf("42: %dn", n);
if (!parse_int("abc", &n)) printf("abc: failedn");
if (!parse_int("42abc", &n)) printf("42abc: failedn");
if (!parse_int("99999999999", &n)) printf("overflow: failedn");
if (parse_int("-17", &n)) printf("-17: %dn", n);
return 0;
}strtol — The Three Parameters
long strtol(const char *str, char **endptr, int base);str— the string to convertendptr— set to point to the first character after the number (pass NULL to discard)base— 10 for decimal, 16 for hex, 0 for auto-detect (0x prefix = hex, 0 prefix = octal)
#include <stdio.h>
#include <stdlib.h>
int main() {
char *end;
/* Parse hex */
long hex = strtol("0xFF", &end, 16); /* or base 0 for auto-detect */
printf("hex: %ldn", hex); /* 255 */
/* Parse octal */
long oct = strtol("0755", NULL, 8);
printf("octal: %ldn", oct); /* 493 */
/* Auto-detect base */
long auto1 = strtol("0xFF", NULL, 0); /* hex: 255 */
long auto2 = strtol("0755", NULL, 0); /* octal: 493 */
long auto3 = strtol("42", NULL, 0); /* decimal: 42 */
return 0;
}Parsing Multiple Numbers From a String
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *data = "10 20 30 40";
char *p = (char*)data;
while (*p != '') {
char *end;
long val = strtol(p, &end, 10);
if (end == p) break; /* no more numbers */
printf("%ldn", val);
p = end; /* advance past the number just parsed */
}
return 0;
}By using endptr as the next starting position, you can parse a sequence of numbers without tokenizing first. This is exactly how simple config parsers and data file readers work.
Other Conversion Functions
| Function | Returns | Error detection |
|---|---|---|
atoi(s) | int | None |
atol(s) | long | None |
atoll(s) | long long | None |
strtol(s, e, base) | long | Full (errno + endptr) |
strtoll(s, e, base) | long long | Full |
strtod(s, e) | double | Full |
strtof(s, e) | float | Full |
Never use atoi, atol, or atoll on input you don’t fully control. Use the strtol family with full error checking. You can paste the parse_int function above into our c code compiler and test it against bad inputs to see the error messages. See our guide on C string functions for related safe alternatives to other string operations.
TL;DR
atoireturns 0 for both “0” and invalid input — cannot distinguish thematoioverflow is undefined behavior — no ERANGE, no error- Use
strtolwith three checks:endptr == str(no digits),*endptr != ''(trailing garbage),errno == ERANGE(overflow) - Reset
errno = 0before callingstrtol— stale values from previous calls cause false positives - Use
strtollfor 64-bit integers,strtod/strtoffor floating point - Chain calls with
endptras the next starting position to parse space-separated number lists - Base 0 auto-detects decimal, hex (0x prefix), and octal (0 prefix)