C言語で文字列から整数へ変換する場面はとてもよくあります。
標準ライブラリのatoi
(atol
, atoll
)は簡単に使えて便利ですが、エラー判定やオーバーフローに弱いという重要な落とし穴があります。
この記事では、これらの関数の役割と使い分け、変換ルール、注意点、実用的なサンプルコードを初心者の方でも理解できるように丁寧に解説します。
atoi, atol, atollの基本
文字列を整数に変換する関数の役割
atoi
、atol
、atoll
は、十進数表記の文字列の先頭部分をそれぞれint
、long
、long long
に変換する関数です。
先頭の空白を読み飛ばし、続く任意の符号(+-)と連続した数字を解釈して整数値にします。
整数の後ろに文字が続いていても、そこまでで変換を止めます。
重要なのは、atoi
系はエラー状態を報告しないことです。
変換できない場合も0
を返すため、入力が「0」なのか「不正な文字列」なのかを区別できません。
必要なヘッダと宣言
これらの関数は<stdlib.h>
に宣言されています。
使うときは必ずインクルードします。
/* 必要なヘッダ */
#include <stdlib.h>
/* プロトタイプ(標準ライブラリで定義済み) */
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr); /* C99以降 */
分かりやすくまとめると次のとおりです。
関数 | 戻り値の型 | 対応する範囲マクロ | 必要ヘッダ |
---|---|---|---|
atoi | int | INT_MIN 〜INT_MAX | <stdlib.h> |
atol | long | LONG_MIN 〜LONG_MAX | <stdlib.h> |
atoll | long long | LLONG_MIN 〜LLONG_MAX | <stdlib.h> |
範囲の具体的な値は実装依存です。
実際の上限・下限は<limits.h>
の各マクロで確認できます。
atoi(atol, atoll)の使い分け
使う型に合わせて選びます。
intに収まる値ならatoi
、より大きな範囲が必要ならatol
やatoll
を使います。
64ビット整数が必要な場面(例えばIDや大きなカウンタ)ならatoll
が適切です。
ただし、厳密なエラー判定やオーバーフロー検出が必要な場面ではstrtol
/strtoll
の使用をおすすめします。
詳しくは後述します。
変換のルールと戻り値
空白と+/-の扱い
変換前に、以下の空白類(いずれもisspace
で判定される)が先頭から読み飛ばされます。
- 半角スペース(’ ’)、タブ(
\t
)、改行(\n
)、復帰(\r
)、垂直タブ(\v
)、フォームフィード(\f
)
その直後にオプションの符号('+'
または'-'
)が1文字だけ解釈されます。
符号の後に続くのは数字である必要があり、例えば「"- 12"
」のように符号と数字の間に空白があると変換は行われません(結果は0
)。
数字の読み取りと停止条件
数字(’0’〜’9’)が連続している範囲を読み取り、最初に数字以外の文字が現れた地点で変換を停止します。
- 「
"123abc"
」は123
まで変換し、'a'
で停止します。 - 「
"abc123"
」は先頭が数字ではないため変換されず、戻り値は0
です。
変換の基数は常に10です。
先頭の「0」や「0x」には特別な意味はありません。
戻り値の型と範囲
戻り値はそれぞれint
、long
、long long
です。
範囲外の値は表現できません。
ただしatoi
系では範囲外(オーバーフロー/アンダーフロー)のときの挙動は未定義です。
安全に扱うにはstrtol
/strtoll
を使います。
失敗時(0)とエラー判定の限界
変換できない場合、atoi
系は0を返します。
このため、「本当に0なのか」「不正入力だったのか」を戻り値だけでは判別できません。
またerrno
も設定されません。
- 「
"0"
」→ 0 - 「
"abc"
」→ 0 - 「
""
」(空文字列)→ 0
オーバーフローの注意
オーバーフローやアンダーフローが発生すると動作は未定義です。
つまり、環境によっては不正な値が返ったり、全く別の結果になったりします。
絶対に避けたい場面ではstrtol
/strtoll
でerrno
と終端位置(endptr
)を確認してください。
使い方の例
基本の使い方
最もシンプルな例です。
数値のみの文字列をint
に変換します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *s = "12345"; /* 十進数の文字列 */
int value = atoi(s); /* 文字列をintに変換 */
/* 変換結果を表示 */
printf("s=\"%s\" -> %d\n", s, value);
return 0;
}
s="12345" -> 12345
ポイントの説明
この例では変換に成功し、12345
が得られます。
ここまでは簡単ですが、atoi
は不正入力やオーバーフローを検出できない点を忘れないでください。
long/long longの例
より大きな値を扱うためにatol
やatoll
を使う例です。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *s_long = "1234567890"; /* longに十分収まる例(多くの環境) */
const char *s_ll = "1234567890123456789"; /* long long向けの大きな値 */
long lv = atol(s_long);
long long llv = atoll(s_ll);
printf("atol(\"%s\") = %ld\n", s_long, lv);
printf("atoll(\"%s\") = %lld\n", s_ll, llv);
return 0;
}
atol("1234567890") = 1234567890
atoll("1234567890123456789") = 1234567890123456789
ポイントの説明
どの型に収まるかは環境のビット幅に依存します。
確実な上限/下限は<limits.h>
のLONG_MAX
/LLONG_MAX
などで確認してください。
空白や+/-を含む文字列の例
先頭空白と符号を含む例です。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *s1 = " -42"; /* 先頭空白と負号 */
const char *s2 = "\t+17\n"; /* タブと正号、末尾に改行 */
int v1 = atoi(s1); /* 空白を飛ばし、-42を取得 */
int v2 = atoi(s2); /* 空白を飛ばし、+17を取得(改行で停止) */
printf("atoi(\"%s\") = %d\n", " -42", v1);
printf("atoi(\"%s\") = %d\n", "\\t+17\\n", v2);
return 0;
}
atoi(" -42") = -42
atoi("\t+17\n") = 17
補足
符号の後に数字が続かない「"- 12"
」のようなケースは変換できません(結果0)。
これはatoi
の仕様です。
“123abc”のように途中で文字がある例
数字が途切れた地点で変換が止まることを確認します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *s1 = "123abc"; /* 123まで有効 */
const char *s2 = "abc123"; /* 先頭が数字でない */
int v1 = atoi(s1); /* 123を取得 */
int v2 = atoi(s2); /* 変換不可 -> 0 */
printf("atoi(\"%s\") = %d\n", s1, v1);
printf("atoi(\"%s\") = %d\n", s2, v2);
return 0;
}
atoi("123abc") = 123
atoi("abc123") = 0
限界の理解
この挙動は便利なこともありますが、「0」と「失敗」の区別ができないため、入力の妥当性が重要な場面では十分ではありません。
簡単な入力チェックのコツ
本当に整数だけで構成されているかを事前にざっくり確認する小さな関数を書いておくと便利です。
以下は「先頭空白→符号→1文字以上の数字→末尾空白のみ」を許す簡易チェックです。
ただしオーバーフローは検出できません。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>
/* 文字列sが「空白* 符号? 数字+ 空白*」の形かを判定する */
bool is_int_like(const char *s) {
if (s == NULL) return false;
/* 先頭空白をスキップ */
const unsigned char *p = (const unsigned char *)s;
while (*p && isspace(*p)) p++;
/* 符号(+/-)があれば1文字だけ進める */
if (*p == '+' || *p == '-') p++;
/* 少なくとも1文字の数字が必要 */
if (!isdigit(*p)) return false;
while (*p && isdigit(*p)) p++;
/* 残りは空白のみならOK */
while (*p) {
if (!isspace(*p)) return false;
p++;
}
return true;
}
int main(void) {
const char *tests[] = { "42", " -7", "+0", "123abc", "- 12", "", NULL };
for (int i = 0; tests[i] != NULL; i++) {
printf("\"%s\" -> %s", tests[i], is_int_like(tests[i]) ? "OK" : "NG");
if (is_int_like(tests[i])) {
/* チェックを通ったらatoiを使う(オーバーフローは依然として未検出) */
printf(", atoi=%d", atoi(tests[i]));
}
putchar('\n');
}
return 0;
}
"42" -> OK, atoi=42
" -7" -> OK, atoi=-7
"+0" -> OK, atoi=0
"123abc" -> NG
"- 12" -> NG
"" -> NG
実務での注意
この方法はあくまで表面的な入力構文の確認です。
桁あふれの検出はできません。
厳密に扱う場合は次章のstrtol
/strtoll
を使います。
よくある疑問と注意点
数値以外を含む文字列はどうなる?
先頭から見て数字の連続部分までが変換対象です。
途中の文字で停止し、それ以降は無視されます。
先頭が数字でなければ0
が返ります。
空文字列やNULLを渡すと?
- 空文字列
""
は変換できず0になります。 NULL
ポインタを渡すのは未定義動作です。多くの環境でクラッシュの原因になります。必ずNULL
チェックを行ってください。
先頭が0や0xの扱い
atoi
系は常に10進数として解釈します。
先頭の0は単なる桁で、"0123"
は123
です。
"0x10"
のような16進数表記は認識しません。
「"0x10"
」は0
(最初の'0'
のみ変換)になります。
動作を確認する小さな例を示します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("atoi(\"0123\") = %d\n", atoi("0123")); /* 123 */
printf("atoi(\"0x10\") = %d\n", atoi("0x10")); /* 0 -> '0'で停止 */
printf("atoi(\"00042\") = %d\n", atoi("00042")); /* 42 */
return 0;
}
atoi("0123") = 123
atoi("0x10") = 0
atoi("00042") = 42
負の数の変換
負号'-'
は1文字のみ認識され、"-42"
は-42
になります。
符号の直後に数字が必要で、"- 42"
のように空白が挟まると変換は行われません(結果0)。
"-0"
は0
です。
エラーを厳密に扱いたい場合
エラー検出やオーバーフロー判定が必要ならatoi
系は使わず、strtol
/strtoll
を使います。
endptr
でどこまで読めたかを確認し、errno
で範囲外(ERANGE)を検出します。
以下はint
相当の範囲に収まるか確認する例です。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
/* 文字列sを厳密にintへ変換し、成功時に*outへ設定して1を返す。失敗時は0を返す。 */
int strict_to_int(const char *s, int *out) {
if (s == NULL || out == NULL) return 0;
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10); /* 10進として変換 */
/* 1) 変換不能(先頭で詰まる)を排除 */
if (s == end) return 0;
/* 2) 末尾にゴミがないか(必要なら末尾空白は許可してもよい) */
while (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r' || *end == '\f' || *end == '\v') end++;
if (*end != '#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
/* 文字列sを厳密にintへ変換し、成功時に*outへ設定して1を返す。失敗時は0を返す。 */
int strict_to_int(const char *s, int *out) {
if (s == NULL || out == NULL) return 0;
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10); /* 10進として変換 */
/* 1) 変換不能(先頭で詰まる)を排除 */
if (s == end) return 0;
/* 2) 末尾にゴミがないか(必要なら末尾空白は許可してもよい) */
while (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r' || *end == '\f' || *end == '\v') end++;
if (*end != '\0') return 0;
/* 3) オーバーフロー/アンダーフロー検出 */
if (errno == ERANGE) return 0;
/* 4) intに収まるか確認 */
if (v < INT_MIN || v > INT_MAX) return 0;
*out = (int)v;
return 1;
}
int main(void) {
const char *tests[] = {
"2147483647", /* INT_MAX(多くの環境) */
"2147483648", /* INT_MAX+1 -> 失敗 */
" -123 ", /* OK(空白あり) */
"123abc", /* 末尾にゴミ -> 失敗 */
"", /* 失敗 */
NULL
};
for (int i = 0; tests[i] != NULL; i++) {
int out = 0;
int ok = strict_to_int(tests[i], &out);
printf("\"%s\" -> %s", tests[i], ok ? "OK" : "NG");
if (ok) printf(", value=%d", out);
putchar('\n');
}
return 0;
}
') return 0;
/* 3) オーバーフロー/アンダーフロー検出 */
if (errno == ERANGE) return 0;
/* 4) intに収まるか確認 */
if (v < INT_MIN || v > INT_MAX) return 0;
*out = (int)v;
return 1;
}
int main(void) {
const char *tests[] = {
"2147483647", /* INT_MAX(多くの環境) */
"2147483648", /* INT_MAX+1 -> 失敗 */
" -123 ", /* OK(空白あり) */
"123abc", /* 末尾にゴミ -> 失敗 */
"", /* 失敗 */
NULL
};
for (int i = 0; tests[i] != NULL; i++) {
int out = 0;
int ok = strict_to_int(tests[i], &out);
printf("\"%s\" -> %s", tests[i], ok ? "OK" : "NG");
if (ok) printf(", value=%d", out);
putchar('\n');
}
return 0;
}
"2147483647" -> OK, value=2147483647
"2147483648" -> NG
" -123 " -> OK, value=-123
"123abc" -> NG
"" -> NG
このアプローチなら「0」と「失敗」を正確に区別でき、範囲外も検出できます。
まとめ
結論として、atoi
(atol
, atoll
)は「ざっくり変換して結果だけ使う」用途には手軽ですが、エラー判定とオーバーフローに関しては未定義動作を含むため危険です。
先頭空白や符号、数字の読み取りと停止条件などの基本仕様を理解しつつ、入力の妥当性や安全性が重要な場面ではstrtol
/strtoll
を使うのがベストプラクティスです。
型の選択はint
/long
/long long
の必要な範囲で行い、実際の上限・下限は<limits.h>
で確認してください。
これらのポイントを押さえれば、文字列から整数への変換を安全かつ確実に行えます。