ユーザー入力やコマンドライン引数は文字列として受け取ることが多く、そのままでは数値計算に使えません。
本記事では、C言語で文字列を数値へ変換する代表的な関数であるatoiとstrtolの使い方と注意点を、初心者向けに丁寧に解説します。
エラーに強く安全な書き方を中心に、実用的なサンプルコードと出力例を示します。
文字列を数値に変換する基本
C言語では、標準入力やファイル、コマンドライン引数などから取得したデータは文字列です。
これを数値に変換するためにatoiやstrtolを使います。
基本的にはstrtolを使うのが安全で、atoiは簡単ですがエラー判定ができません。
先頭の空白や符号の扱い
どちらの関数も、先頭の空白文字を自動的に読み飛ばし、+や-の符号を受け付けます。
空白とは、半角スペース、タブ、改行などisspaceで判定される文字です。
- 例:
" -42"は負の42に変換されます。 - ただし、全角の空白や全角の数字・符号は変換できません(例:
"-123"は失敗)。必要なら前処理が必要です。
先頭の空白と単一の符号はOK、その他の記号や途中の文字はNGという感覚を持つと理解しやすいです。
intとlongの違い
atoiはintを返し、strtolはlongを返します。
ビット幅は処理系依存で、代表的には以下のようになります。
- 32ビット環境やWindows 64ビット:
intは32ビット、longも32ビット。 - Linuxの多くの64ビット環境:
intは32ビット、longは64ビット。
正しい範囲は<limits.h>のINT_MIN、INT_MAX、LONG_MIN、LONG_MAXで確認します。
ビット数を仮定せず、常にマクロで範囲をチェックするのが安全です。
atoiの使い方と注意点
atoiは簡単に使える反面、入力エラーや範囲超過の検出ができません。
学習目的としての動作確認には向きますが、実務コードでは推奨されません。
atoiで変換できる文字列の形式
- 先頭空白の読み飛ばし、
+/-の符号に対応。 - 連続する十進数字を読み取り、
intとして返します。 - 非数字が途中にあると、そこで読み取りをやめますが、
atoiは停止位置を教えてくれません。
サンプル(出力あり):
// 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: 十六進数としてa〜fも許可。base == 0: 自動判別。先頭0x/0Xなら16進、先頭0なら8進、それ以外は10進。
自動判別で"08"のように8進に不正な桁が来ると、"0"だけ読んで'8'で停止します。
結果を使う前にendptrの確認が必須です。
longの範囲で結果をチェック
範囲外の場合、strtolはLONG_MAXまたはLONG_MINを返し、errnoにERANGEを設定します。
呼び出し前にerrno = 0をセットし、戻った後にerrnoを確認します。
空白と符号(+/-)の扱い
strtolは先頭空白の読み飛ばしと単一の+/-を受け付けます。
一方、全角文字や末尾の不要文字はエラー扱いにすべきです。
必要ならendptrを使って末尾まで消費できたか検証します。
具体例: どこまで読めたか、基数、範囲エラーを確認する
以下はstrtolの実用的な呼び方の例です。
// 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が欲しいときのキャスト方法
strtolはlongを返します。
intが欲しい場合、必ずINT_MIN/INT_MAXで範囲チェックしてからキャストします。
以下は、十進整数文字列を厳密にintへ変換する安全な関数例です。
// 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へ変換できています。
入力チェックの手順
堅牢な変換手順は次の通りです。
errno = 0にする。strtol(s, &end, base)で変換する。end == sなら数字が無く失敗。errno == ERANGEなら範囲外で失敗。- 必要なら
isspaceでend以降の空白を飛ばして、*end == '\0'か確認。 intが必要ならINT_MIN/INT_MAXで範囲チェックしてからキャスト。
この手順をテンプレート化しておくと、入力値の扱いが安定します。
よくある落とし穴
以下の落とし穴は初心者が遭遇しやすいため、意識して避けましょう。
| 落とし穴 | 何が問題か | 対策 |
|---|---|---|
atoiで0の意味が曖昧 | 入力"0"と"abc"が区別できない | strtolを使いendptrで確認 |
| 範囲外の未定義動作 | atoiは範囲外が未定義 | strtol+errnoでERANGEを検出 |
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点も併せて押さえておくと、実務でも通用する堅牢なコードが書けます。
