Skip to content

/commands/c/convert-string-to-int

c convert string to int safely with strtol (not atoi)

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
C
versions
C89+ (strtol), C99+ (strtoll)
os
any

To convert a string to an integer in C, use strtol(str, &end, 10) from <stdlib.h>: it returns the value as a long, sets end to the first character it did not use, and sets errno to ERANGE when the number is too large. Check all three: end == str means no digits, *end != '\0' means trailing garbage. atoi is shorter but cannot tell "0" from "abc".

A checked conversion

The example uses Clang 20 from the ziglang package on PyPI, run through uv; cc below stands for it. Write gcc or clang on your machine:

bash
cc() { uv run -q --no-project --python 3.13 --with ziglang==0.15.1 python -m ziglang cc -fno-color-diagnostics "$@"; }
cat > parse.c <<'EOF'
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

/* Returns 0 on success and stores the value in *out; -1 on error. */
static int parse_int(const char *s, int *out) {
    char *end;
    errno = 0;
    long v = strtol(s, &end, 10);
    if (end == s) return -1;                       /* no digits at all */
    while (*end == ' ' || *end == '\n') end++;     /* allow trailing spaces/newline */
    if (*end != '\0') return -1;                   /* trailing garbage */
    if (errno == ERANGE || v < INT_MIN || v > INT_MAX) return -1;  /* out of range */
    *out = (int)v;
    return 0;
}

int main(void) {
    const char *inputs[] = {"42", "  -17", "12abc", "abc", "", "99999999999999999999", "2147483648", " 8 "};
    for (size_t i = 0; i < sizeof inputs / sizeof *inputs; i++) {
        int n = 0;
        int rc = parse_int(inputs[i], &n);
        printf("%-24s atoi=%-11d parse_int=%s", inputs[i][0] == '\0' ? "(empty)" : inputs[i],
               i == 5 || i == 6 ? 0 : atoi(inputs[i]), rc == 0 ? "ok " : "error");
        if (rc == 0) printf(" %d", n);
        printf("\n");
    }
    printf("hex ff = %ld, base 0 \"0x1A\" = %ld\n", strtol("ff", NULL, 16), strtol("0x1A", NULL, 0));
    return 0;
}
EOF
cc -std=c17 -Wall -Wextra parse.c -o parse && ./parse

Output

42                       atoi=42          parse_int=ok  42
  -17                    atoi=-17         parse_int=ok  -17
12abc                    atoi=12          parse_int=error
abc                      atoi=0           parse_int=error
(empty)                  atoi=0           parse_int=error
99999999999999999999     atoi=0           parse_int=error
2147483648               atoi=0           parse_int=error
 8                       atoi=8           parse_int=ok  8
hex ff = 255, base 0 "0x1A" = 26

atoi returns 12 for "12abc" and 0 for "abc" and "" without any sign of an error, while the checked version rejects all three. The two out-of-range inputs are not passed to atoi at all, because its behaviour is undefined for them. Leading white space is skipped by strtol itself, and the loop after it accepts trailing spaces and the newline that fgets leaves at the end of a line.

Functions

FunctionReturnsErrors detected
strtol(s, &end, base)longNo digits (end == s), trailing text (*end), overflow (errno = ERANGE)
strtoll / strtoul / strtoulllong long / unsigned long / unsigned long longSame as strtol (C99)
atoi(s) / atol(s)int / longNone; undefined behaviour on overflow
sscanf(s, "%d", &n)Number of fields convertedNo digits (returns 0); overflow is undefined behaviour
strtol(s, NULL, 0)longBase from the prefix: 0x hex, 0 octal, otherwise decimal

Pitfalls

  • Missing header. Calling strtol or atoi without #include <stdlib.h> fails with implicit declaration of function on current compilers.
  • Leading zeros with base 0. strtol("08", NULL, 0) reads 0 as an octal prefix and stops at 8; use base 10 for user input.
  • long vs int. On 64-bit Linux and macOS long is 64 bits, on 64-bit Windows it is 32 bits: the range check against INT_MAX keeps the code portable.
  • Unsigned parsing. strtoul("-1", ...) does not fail; it returns ULONG_MAX. Check for a minus sign yourself if negative input must be rejected.

Confirm it worked

Test the cases above (empty, letters, trailing text, a value just past INT_MAX, a newline at the end) and check that each is accepted or rejected as you expect.

FAQ

What is wrong with atoi?

atoi cannot report errors: it returns 0 both for "0" and for "abc", and the behaviour is undefined when the value does not fit in an int. strtol reports where parsing stopped and sets errno to ERANGE on overflow.

Why set errno to 0 before calling strtol?

strtol sets errno only when an error happens; it never resets it. Without errno = 0 first, an old ERANGE from an earlier call would look like a new overflow.

How do I parse into an int instead of a long?

Parse with strtol, then check that the value is between INT_MIN and INT_MAX before casting. On platforms where long is 64 bits, a value can fit in long but not in int.

Sources