閉じる

C言語のatoiとstrtolで文字列を数値に変換する使い方と注意点

ユーザー入力やコマンドライン引数は文字列として受け取ることが多く、そのままでは数値計算に使えません。

本記事では、C言語で文字列を数値へ変換する代表的な関数であるatoistrtolの使い方と注意点を、初心者向けに丁寧に解説します。

エラーに強く安全な書き方を中心に、実用的なサンプルコードと出力例を示します。

文字列を数値に変換する基本

C言語では、標準入力やファイル、コマンドライン引数などから取得したデータは文字列です。

これを数値に変換するためにatoistrtolを使います。

基本的にはstrtolを使うのが安全で、atoiは簡単ですがエラー判定ができません。

先頭の空白や符号の扱い

どちらの関数も、先頭の空白文字を自動的に読み飛ばし、+-の符号を受け付けます。

空白とは、半角スペース、タブ、改行などisspaceで判定される文字です。

  • 例: " -42" は負の42に変換されます。
  • ただし、全角の空白や全角の数字・符号は変換できません(例: "-123" は失敗)。必要なら前処理が必要です。

先頭の空白と単一の符号はOK、その他の記号や途中の文字はNGという感覚を持つと理解しやすいです。

intとlongの違い

atoiintを返し、strtollongを返します。

ビット幅は処理系依存で、代表的には以下のようになります。

  • 32ビット環境やWindows 64ビット: intは32ビット、longも32ビット。
  • Linuxの多くの64ビット環境: intは32ビット、longは64ビット。

正しい範囲は<limits.h>INT_MININT_MAXLONG_MINLONG_MAXで確認します。

ビット数を仮定せず、常にマクロで範囲をチェックするのが安全です。

atoiの使い方と注意点

atoiは簡単に使える反面、入力エラーや範囲超過の検出ができません。

学習目的としての動作確認には向きますが、実務コードでは推奨されません。

atoiで変換できる文字列の形式

  • 先頭空白の読み飛ばし、+/-の符号に対応。
  • 連続する十進数字を読み取り、intとして返します。
  • 非数字が途中にあると、そこで読み取りをやめますが、atoiは停止位置を教えてくれません。

サンプル(出力あり):

C言語
// atoi_demo.c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *cases[] = {
        "123",        // 正常
        "   -42",     // 先頭空白と符号
        "123abc",     // 途中で非数字
        "abc",        // 数字が先頭にない
        "2147483647", // INT_MAX (32ビット環境の例)
        "2147483648", // オーバーフロー(未定義動作)
    };

    for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); ++i) {
        int v = atoi(cases[i]); // エラー判定はできない
        printf("input=\"%s\" -> atoi=%d\n", cases[i], v);
    }
    return 0;
}

実行例(一例、環境により異なる可能性があります):

input="123" -> atoi=123
input="   -42" -> atoi=-42
input="123abc" -> atoi=123
input="abc" -> atoi=0
input="2147483647" -> atoi=2147483647
input="2147483648" -> atoi=2147483647

最後の行は未定義動作です。

たまたまこの出力になっているだけで、他環境では別の値になったり、診断もなく誤った結果を返します。

atoiの戻り値と0の区別に注意

atoiは失敗時にエラーコードを返しません。

入力が"0"だったのか、"abc"で変換できずに0になったのか、結果だけでは区別できません

この曖昧さがatoiの最大の弱点です。

atoiはエラー判定ができない

atoiにはエラーを知る仕組みがありません

部分的にしか読めなかったのか、まったく読めなかったのか、範囲外だったのかが分かりません。

入力検証が必要な場面では使用を避けましょう。

atoiはオーバーフローで未定義動作

intの範囲を超える値を与えると未定義動作です。

例として"2147483648"を32ビットintの環境で与えると挙動は不定になります。

これもatoiを避けるべき理由です。

strtolの使い方と注意点

strtolは、変換の成否やどこまで読めたか、範囲外だったかを検出できます。

安全に文字列を整数へ変換したいならstrtol一択です。

strtolの引数

関数の宣言は以下の通りです。

  • long strtol(const char *nptr, char **endptr, int base);

引数の意味は次の通りです。

  • nptr: 変換する文字列。
  • endptr: 読み取った最後の位置の次を受け取るポインタ。不要ならNULL
  • base: 基数(2〜36または0)。0は自動判別。

endptrで変換できた位置を確認

endptr「どこまでが数値だったか」を示します。

endptr == nptrなら1文字も読めていません。

逆に末尾まで読めていればendptrは終端文字'\0'を指します。

基数(base)の指定と自動判別

  • base == 10: 十進数として読みます。
  • base == 16: 十六進数としてafも許可。
  • base == 0: 自動判別。先頭0x/0Xなら16進、先頭0なら8進、それ以外は10進。
注意

自動判別で"08"のように8進に不正な桁が来ると、"0"だけ読んで'8'で停止します。

結果を使う前にendptrの確認が必須です。

longの範囲で結果をチェック

範囲外の場合、strtolLONG_MAXまたはLONG_MINを返し、errnoERANGEを設定します。

呼び出し前にerrno = 0をセットし、戻った後にerrnoを確認します。

空白と符号(+/-)の扱い

strtolは先頭空白の読み飛ばしと単一の+/-を受け付けます。

一方、全角文字や末尾の不要文字はエラー扱いにすべきです。

必要ならendptrを使って末尾まで消費できたか検証します。

具体例: どこまで読めたか、基数、範囲エラーを確認する

以下はstrtolの実用的な呼び方の例です。

C言語
// strtol_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>

static void try_parse(const char *s, int base) {
    errno = 0;                  // ERANGE検出のため必ず0に初期化
    char *end = NULL;
    long v = strtol(s, &end, base);

    // 変換結果の報告
    printf("input=\"%s\", base=%d -> value=%ld\n", s, base, v);

    // どこで停止したか
    if (end == s) {
        printf("  note: no digits were consumed (conversion failed)\n");
    } else {
        printf("  consumed %td chars, stopped at '%c' (0x%02X)\n",
               end - s, *end ? *end : '\
// strtol_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
static void try_parse(const char *s, int base) {
errno = 0;                  // ERANGE検出のため必ず0に初期化
char *end = NULL;
long v = strtol(s, &end, base);
// 変換結果の報告
printf("input=\"%s\", base=%d -> value=%ld\n", s, base, v);
// どこで停止したか
if (end == s) {
printf("  note: no digits were consumed (conversion failed)\n");
} else {
printf("  consumed %td chars, stopped at '%c' (0x%02X)\n",
end - s, *end ? *end : '\\0', (unsigned char)(*end));
}
// 範囲エラー
if (errno == ERANGE) {
printf("  error: ERANGE (out of range), result was %s\n",
(v == LONG_MAX) ? "LONG_MAX" :
(v == LONG_MIN) ? "LONG_MIN" : "clamped");
}
// 末尾の空白を許容し、それ以外が残っていないかを確認
const char *p = end;
while (*p && isspace((unsigned char)*p)) { p++; }
if (*p != '\0') {
printf("  warn: trailing non-space characters remain: \"%s\"\n", p);
}
}
int main(void) {
try_parse("   -42", 10);   // 先頭空白と符号
try_parse("123abc", 10);   // 途中文字
try_parse("0x1f", 0);      // 自動判別(16進)
try_parse("010", 0);       // 自動判別(8進)
try_parse("08", 0);        // 8進としては不正桁 -> "0"のみ消費
try_parse("9999999999999999999999", 10); // ほぼ確実にオーバーフロー
return 0;
}
', (unsigned char)(*end)); } // 範囲エラー if (errno == ERANGE) { printf(" error: ERANGE (out of range), result was %s\n", (v == LONG_MAX) ? "LONG_MAX" : (v == LONG_MIN) ? "LONG_MIN" : "clamped"); } // 末尾の空白を許容し、それ以外が残っていないかを確認 const char *p = end; while (*p && isspace((unsigned char)*p)) { p++; } if (*p != '
// strtol_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
static void try_parse(const char *s, int base) {
errno = 0;                  // ERANGE検出のため必ず0に初期化
char *end = NULL;
long v = strtol(s, &end, base);
// 変換結果の報告
printf("input=\"%s\", base=%d -> value=%ld\n", s, base, v);
// どこで停止したか
if (end == s) {
printf("  note: no digits were consumed (conversion failed)\n");
} else {
printf("  consumed %td chars, stopped at '%c' (0x%02X)\n",
end - s, *end ? *end : '\\0', (unsigned char)(*end));
}
// 範囲エラー
if (errno == ERANGE) {
printf("  error: ERANGE (out of range), result was %s\n",
(v == LONG_MAX) ? "LONG_MAX" :
(v == LONG_MIN) ? "LONG_MIN" : "clamped");
}
// 末尾の空白を許容し、それ以外が残っていないかを確認
const char *p = end;
while (*p && isspace((unsigned char)*p)) { p++; }
if (*p != '\0') {
printf("  warn: trailing non-space characters remain: \"%s\"\n", p);
}
}
int main(void) {
try_parse("   -42", 10);   // 先頭空白と符号
try_parse("123abc", 10);   // 途中文字
try_parse("0x1f", 0);      // 自動判別(16進)
try_parse("010", 0);       // 自動判別(8進)
try_parse("08", 0);        // 8進としては不正桁 -> "0"のみ消費
try_parse("9999999999999999999999", 10); // ほぼ確実にオーバーフロー
return 0;
}
') { printf(" warn: trailing non-space characters remain: \"%s\"\n", p); } } int main(void) { try_parse(" -42", 10); // 先頭空白と符号 try_parse("123abc", 10); // 途中文字 try_parse("0x1f", 0); // 自動判別(16進) try_parse("010", 0); // 自動判別(8進) try_parse("08", 0); // 8進としては不正桁 -> "0"のみ消費 try_parse("9999999999999999999999", 10); // ほぼ確実にオーバーフロー return 0; }
実行結果
input="   -42", base=10 -> value=-42
  consumed 6 chars, stopped at '
input="   -42", base=10 -> value=-42
consumed 6 chars, stopped at '\0' (0x00)
input="123abc", base=10 -> value=123
consumed 3 chars, stopped at 'a' (0x61)
warn: trailing non-space characters remain: "abc"
input="0x1f", base=0 -> value=31
consumed 4 chars, stopped at '\0' (0x00)
input="010", base=0 -> value=8
consumed 3 chars, stopped at '\0' (0x00)
input="08", base=0 -> value=0
consumed 1 chars, stopped at '8' (0x38)
warn: trailing non-space characters remain: "8"
input="9999999999999999999999", base=10 -> value=9223372036854775807
consumed 22 chars, stopped at '\0' (0x00)
error: ERANGE (out of range), result was LONG_MAX
' (0x00) input="123abc", base=10 -> value=123 consumed 3 chars, stopped at 'a' (0x61) warn: trailing non-space characters remain: "abc" input="0x1f", base=0 -> value=31 consumed 4 chars, stopped at '
input="   -42", base=10 -> value=-42
consumed 6 chars, stopped at '\0' (0x00)
input="123abc", base=10 -> value=123
consumed 3 chars, stopped at 'a' (0x61)
warn: trailing non-space characters remain: "abc"
input="0x1f", base=0 -> value=31
consumed 4 chars, stopped at '\0' (0x00)
input="010", base=0 -> value=8
consumed 3 chars, stopped at '\0' (0x00)
input="08", base=0 -> value=0
consumed 1 chars, stopped at '8' (0x38)
warn: trailing non-space characters remain: "8"
input="9999999999999999999999", base=10 -> value=9223372036854775807
consumed 22 chars, stopped at '\0' (0x00)
error: ERANGE (out of range), result was LONG_MAX
' (0x00) input="010", base=0 -> value=8 consumed 3 chars, stopped at '
input="   -42", base=10 -> value=-42
consumed 6 chars, stopped at '\0' (0x00)
input="123abc", base=10 -> value=123
consumed 3 chars, stopped at 'a' (0x61)
warn: trailing non-space characters remain: "abc"
input="0x1f", base=0 -> value=31
consumed 4 chars, stopped at '\0' (0x00)
input="010", base=0 -> value=8
consumed 3 chars, stopped at '\0' (0x00)
input="08", base=0 -> value=0
consumed 1 chars, stopped at '8' (0x38)
warn: trailing non-space characters remain: "8"
input="9999999999999999999999", base=10 -> value=9223372036854775807
consumed 22 chars, stopped at '\0' (0x00)
error: ERANGE (out of range), result was LONG_MAX
' (0x00) input="08", base=0 -> value=0 consumed 1 chars, stopped at '8' (0x38) warn: trailing non-space characters remain: "8" input="9999999999999999999999", base=10 -> value=9223372036854775807 consumed 22 chars, stopped at '
input="   -42", base=10 -> value=-42
consumed 6 chars, stopped at '\0' (0x00)
input="123abc", base=10 -> value=123
consumed 3 chars, stopped at 'a' (0x61)
warn: trailing non-space characters remain: "abc"
input="0x1f", base=0 -> value=31
consumed 4 chars, stopped at '\0' (0x00)
input="010", base=0 -> value=8
consumed 3 chars, stopped at '\0' (0x00)
input="08", base=0 -> value=0
consumed 1 chars, stopped at '8' (0x38)
warn: trailing non-space characters remain: "8"
input="9999999999999999999999", base=10 -> value=9223372036854775807
consumed 22 chars, stopped at '\0' (0x00)
error: ERANGE (out of range), result was LONG_MAX
' (0x00) error: ERANGE (out of range), result was LONG_MAX

範囲外の検出と末尾の検証が簡単にできることが分かります。

atoiとstrtolの違いと使い分け

両者の違いをまとめると以下の通りです。

  • atoi: 簡単、でもエラー検出なし・範囲外は未定義
  • strtol: 少し手間、でもエラーや停止位置、範囲外が判定できる

初心者はstrtolを使う理由

初心者こそstrtolを使うべきです。

なぜなら、入力エラーが起きるのは普通だからです。

atoiでは「0かどうか」程度しか分からず、バグを埋め込みやすくなります。

strtolを使えば、未入力、部分変換、範囲外、不要文字残りを確実に検出できます。

intが欲しいときのキャスト方法

strtollongを返します。

intが欲しい場合、必ずINT_MIN/INT_MAXで範囲チェックしてからキャストします。

以下は、十進整数文字列を厳密にintへ変換する安全な関数例です。

C言語
// strtol_to_int.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>

int parse_int_strict(const char *s, int *out) {
    if (s == NULL || out == NULL) return 0;

    // 先頭空白はstrtolが読み飛ばす
    errno = 0;
    char *end = NULL;
    long v = strtol(s, &end, 10);

    // 1文字も読めなかった
    if (end == s) return 0;

    // 範囲外(underflow/overflow)
    if (errno == ERANGE) return 0;

    // 末尾は空白のみを許容(他の文字があればエラー)
    while (*end && isspace((unsigned char)*end)) end++;
    if (*end != '
// strtol_to_int.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
int parse_int_strict(const char *s, int *out) {
if (s == NULL || out == NULL) return 0;
// 先頭空白はstrtolが読み飛ばす
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10);
// 1文字も読めなかった
if (end == s) return 0;
// 範囲外(underflow/overflow)
if (errno == ERANGE) return 0;
// 末尾は空白のみを許容(他の文字があればエラー)
while (*end && isspace((unsigned char)*end)) end++;
if (*end != '\0') return 0;
// intの範囲に収まるか
if (v < INT_MIN || v > INT_MAX) return 0;
*out = (int)v;
return 1;
}
int main(void) {
const char *cases[] = {
"123",
"  +77",
"42   ",
"12abc",
"2147483647",  // INT_MAX
"2147483648",  // INT_MAX+1 -> 失敗
"   ",         // 空白のみ -> 失敗
"-0010",       // 先頭ゼロと符号 -> OK (-10)
};
for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); ++i) {
int value = 0;
int ok = parse_int_strict(cases[i], &value);
if (ok) {
printf("OK: \"%s\" -> %d\n", cases[i], value);
} else {
printf("NG: \"%s\"\n", cases[i]);
}
}
return 0;
}
') return 0; // intの範囲に収まるか if (v < INT_MIN || v > INT_MAX) return 0; *out = (int)v; return 1; } int main(void) { const char *cases[] = { "123", " +77", "42 ", "12abc", "2147483647", // INT_MAX "2147483648", // INT_MAX+1 -> 失敗 " ", // 空白のみ -> 失敗 "-0010", // 先頭ゼロと符号 -> OK (-10) }; for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); ++i) { int value = 0; int ok = parse_int_strict(cases[i], &value); if (ok) { printf("OK: \"%s\" -> %d\n", cases[i], value); } else { printf("NG: \"%s\"\n", cases[i]); } } return 0; }
実行結果
OK: "123" -> 123
OK: "  +77" -> 77
OK: "42   " -> 42
NG: "12abc"
OK: "2147483647" -> 2147483647
NG: "2147483648"
NG: "   "
OK: "-0010" -> -10

「数値だけで構成されているか」「範囲内か」を確実に検査した上でintへ変換できています。

入力チェックの手順

堅牢な変換手順は次の通りです。

  1. errno = 0にする。
  2. strtol(s, &end, base)で変換する。
  3. end == sなら数字が無く失敗。
  4. errno == ERANGEなら範囲外で失敗。
  5. 必要ならisspaceend以降の空白を飛ばして、*end == '\0'か確認。
  6. intが必要ならINT_MIN/INT_MAXで範囲チェックしてからキャスト。

この手順をテンプレート化しておくと、入力値の扱いが安定します。

よくある落とし穴

以下の落とし穴は初心者が遭遇しやすいため、意識して避けましょう。

落とし穴何が問題か対策
atoi0の意味が曖昧入力"0""abc"が区別できないstrtolを使いendptrで確認
範囲外の未定義動作atoiは範囲外が未定義strtol+errnoERANGEを検出
base=0の罠"08"のように8進の不正桁で部分変換される末尾まで消費できたかendptrで検証
末尾のゴミ"123abc"をそのまま採用してしまう末尾の空白以外が残っていないか確認
errnoのリセット忘れ以前のエラーが残って誤判定呼出し前にerrno=0
全角数字・符号strtolは半角のみ対応入力段階で半角に正規化する
型幅の思い込みlongのビット幅は環境依存LONG_MIN/LONG_MAXで判定、必要に応じstrtollも検討
落とし穴と対策

用途別の目安として、ユーザー入力やファイルの値を厳密に扱うときはstrtol、内部で確実に整形済みのリテラルを読むだけならatoiでも構いませんが、基本は常にstrtolを使うと覚えておくと安全です。

まとめ

本記事では、C言語で文字列を安全に数値へ変換する方法としてstrtolを中心に解説しました。

atoiは簡単ですがエラー検出不能・範囲外は未定義動作という本質的な弱点があります。

一方strtolならendptrで部分変換を見分け、errno==ERANGEで範囲外を検出し、末尾の不要文字も排除できます。

初心者の方はまずstrtolテンプレートを手元に用意し、必要に応じてintへ安全にキャストする流れを習得しましょう。

環境依存の型幅は<limits.h>のマクロで必ず確認する、全角文字は前処理で正規化する、という2点も併せて押さえておくと、実務でも通用する堅牢なコードが書けます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!