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:

  1. Returns 0 for both the string “0” and any invalid input — indistinguishable
  2. Stops at the first non-digit without signaling that it did
  3. 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 convert
  • endptr — 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

FunctionReturnsError detection
atoi(s)intNone
atol(s)longNone
atoll(s)long longNone
strtol(s, e, base)longFull (errno + endptr)
strtoll(s, e, base)long longFull
strtod(s, e)doubleFull
strtof(s, e)floatFull

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

  • atoi returns 0 for both “0” and invalid input — cannot distinguish them
  • atoi overflow is undefined behavior — no ERANGE, no error
  • Use strtol with three checks: endptr == str (no digits), *endptr != '' (trailing garbage), errno == ERANGE (overflow)
  • Reset errno = 0 before calling strtol — stale values from previous calls cause false positives
  • Use strtoll for 64-bit integers, strtod/strtof for floating point
  • Chain calls with endptr as the next starting position to parse space-separated number lists
  • Base 0 auto-detects decimal, hex (0x prefix), and octal (0 prefix)