C言語でユーザー入力や設定ファイルを扱うとき、文字列を整数に変換する場面はとても多いです。
その代表的な関数がatoiとatolです。
しかし、これらは一見便利な反面、エラー検出がしにくいなどの落とし穴もあります。
本記事では、atoi・atolの基本から、実務で知っておくべき注意点、そしてより安全な代替関数までを、図解とサンプルコードを交えて詳しく解説します。
C言語のatoi・atolとは
atoi・atolの基本概要
atoiとatolは、それぞれ文字列を整数値に変換する標準ライブラリ関数です。
C言語でユーザー入力やファイルから読み込んだテキストを数値として扱いたいときに利用されます。
atoi… 文字列をint型に変換する関数atol… 文字列をlong型に変換する関数
これらはどちらも<stdlib.h>で宣言されており、C言語の古くからある関数です。

この図から分かるように、atoi・atolは文字列から整数への変換を行う入り口として利用されます。
文字列からint/longへ変換する目的
プログラムは多くの場合、人間が読み書きしやすい文字列でデータを外部とやり取りします。
しかし、計算や比較、条件分岐を行うときには数値型で扱う必要があります。
例えば、次のような場面です。
- コマンドライン引数
argvで指定されたポート番号を数値として扱う - 設定ファイルに記載された「タイムアウト秒」を読み込み、処理時間の上限に使う
- ユーザーが入力した「年齢」「数量」などを計算に使う
このようなケースでは、まず文字列→整数への変換を行わなければなりません。
その役割を担うのがatoiやatolです。
atoiとatolの違い
atoiとatolの違いは非常にシンプルで、返す型が異なるだけです。
atoi: 返り値はintatol: 返り値はlong
この違いは、扱える数値の範囲に直結します。
| 関数 | 返り値の型 | 一般的なビット数(多くの環境) | 概ねの範囲(符号付き) |
|---|---|---|---|
| atoi | int | 32ビット | 約 -2×10^9 ~ 2×10^9 |
| atol | long | 32または64ビット | 環境に依存 |
long型のビット数や範囲は環境依存です。
後述しますが、特に64ビット環境ではlongのサイズに差が出るため、atolを使うときには注意が必要です。
atoiの使い方と仕様
atoiの関数プロトタイプとヘッダファイル
atoiは標準ライブラリ<stdlib.h>で宣言されている関数です。
プロトタイプは次のとおりです。
int atoi(const char *nptr);
- 引数
nptrには数値を表す文字列を渡します。 - 返り値は変換後のint値です。
- 変換に失敗した場合やオーバーフローが発生した場合でも、明確なエラーコードは返しません。
使用するには、ソースファイルの先頭付近で次のように記述します。
#include <stdio.h>
#include <stdlib.h> /* atoiの宣言があるヘッダ */
atoiで文字列をintに変換する基本的なコード例
まずは、最も基本的な使い方の例です。
#include <stdio.h>
#include <stdlib.h> /* atoiを使うために必要 */
int main(void)
{
/* 数値を表す文字列 */
const char *str1 = "12345";
const char *str2 = "-42";
/* atoiで文字列をintに変換 */
int value1 = atoi(str1);
int value2 = atoi(str2);
/* 結果を表示 */
printf("str1 = \"%s\" → value1 = %d\n", str1, value1);
printf("str2 = \"%s\" → value2 = %d\n", str2, value2);
return 0;
}
str1 = "12345" → value1 = 12345
str2 = "-42" → value2 = -42
この例のように、atoiは数字のみからなる文字列だけでなく、先頭に符号が付いた整数も正しく変換します。
先頭の空白・符号(+/−)・数字の扱い
atoiは、文字列を次のようなルールで読み取ります。

このルールを文章で整理すると次のようになります。
- 先頭の空白文字(スペース、タブなど)を読み飛ばす。
- 続く任意の1文字の符号(
'+'または'-')を解釈する。 - 次に現れる数字の連続部分を整数として解釈する。
- 数字以外の文字が出たところで変換を終了し、それ以降は無視する。
いくつかの例を実際に試してみます。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const char *s1 = " 256"; /* 先頭に空白 */
const char *s2 = "\t-100abc"; /* タブ + 負号 + 数字 + 余分な文字 */
const char *s3 = "+42"; /* プラス符号付き */
const char *s4 = " +0030xyz"; /* ゼロ埋め + 余分な文字 */
int v1 = atoi(s1);
int v2 = atoi(s2);
int v3 = atoi(s3);
int v4 = atoi(s4);
printf("\"%s\" → %d\n", s1, v1);
printf("\"%s\" → %d\n", s2, v2);
printf("\"%s\" → %d\n", s3, v3);
printf("\"%s\" → %d\n", s4, v4);
return 0;
}
" 256" → 256
" -100abc" → -100
"+42" → 42
" +0030xyz" → 30
このように、先頭の空白は無視され、符号と数字の連続部分だけが対象になります。
先頭にゼロが付いていても、0030は整数の30として扱われます。
数値以外の文字が含まれる場合の動作
atoiは途中に数値以外の文字が出たところで変換を止めます。
その結果、先頭が数字で始まる限り、途中に文字が混じっていても「とりあえず」変換されてしまいます。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const char *s1 = "123abc"; /* 数字のあとに文字 */
const char *s2 = "99.5"; /* 小数点を含む */
const char *s3 = "10,000"; /* カンマ区切り */
const char *s4 = "abc123"; /* 先頭が数字でない */
printf("\"%s\" → %d\n", s1, atoi(s1));
printf("\"%s\" → %d\n", s2, atoi(s2));
printf("\"%s\" → %d\n", s3, atoi(s3));
printf("\"%s\" → %d\n", s4, atoi(s4));
return 0;
}
"123abc" → 123
"99.5" → 99
"10,000" → 10
"abc123" → 0
ここで特に注意すべき点は、先頭が数字でない文字列は0として扱われてしまうことです。
本当に0なのか、不正な文字列なのかを区別できないという問題につながります。
intの範囲外(オーバーフロー)時の挙動
atoiで変換した結果がintの表現可能範囲を超える場合、C標準規格では動作は未定義(undefined behavior)とされています。
未定義動作とは、
- 異常終了するかもしれない
- 間違った値になるかもしれない
- たまたま正しく見える動作をするかもしれない
といった、コンパイラや環境に依存して予測できない挙動を意味します。
例として、意図的に大きな値を変換してみます。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h> /* INT_MAX, INT_MIN */
int main(void)
{
const char *s1 = "2147483647"; /* 32bit int の最大値(例) */
const char *s2 = "2147483648"; /* 最大値 + 1 → オーバーフロー */
const char *s3 = "-2147483648"; /* 最小値(例) */
const char *s4 = "-2147483649"; /* 最小値 - 1 → オーバーフロー */
printf("INT_MAX = %d\n", INT_MAX);
printf("INT_MIN = %d\n\n", INT_MIN);
printf("\"%s\" → %d\n", s1, atoi(s1));
printf("\"%s\" → %d\n", s2, atoi(s2));
printf("\"%s\" → %d\n", s3, atoi(s3));
printf("\"%s\" → %d\n", s4, atoi(s4));
return 0;
}
INT_MAX = 2147483647
INT_MIN = -2147483648
"2147483647" → 2147483647
"2147483648" → 2147483647 ← 例: 環境によってはこうなることもある
"-2147483648" → -2147483648
"-2147483649" → -2147483648 ← 例: 環境によってはこうなることもある
上記のような挙動になる環境もありますが、これはたまたまそうなっているだけで、規格上は保証されていません。
オーバーフローを検出したい場合はatoiではなく、後述するstrtolを使うべきです。
atolの使い方と仕様
atolの関数プロトタイプとヘッダファイル
atolも<stdlib.h>で宣言されている関数で、プロトタイプは次のとおりです。
long atol(const char *nptr);
- 引数
nptrには数値文字列を渡します。 - 返り値は変換後のlong値です。
- 変換ルールは
atoiとほぼ同じで、先頭の空白・符号・数字の扱いも同様です。
atolで文字列をlongに変換する基本的なコード例
基本的な使い方を示すサンプルです。
#include <stdio.h>
#include <stdlib.h> /* atolを使うために必要 */
int main(void)
{
const char *s1 = "1234567890";
const char *s2 = "-9876543210";
long v1 = atol(s1);
long v2 = atol(s2);
printf("\"%s\" → %ld\n", s1, v1);
printf("\"%s\" → %ld\n", s2, v2);
return 0;
}
"1234567890" → 1234567890
"-9876543210" → -9876543210
書式指定子%ldを使ってlong型を出力している点にも注意してください。
long型の範囲と環境依存性
long型は、環境によってビット数や範囲が異なる型です。
特に、32ビット環境と64ビット環境で注意が必要です。
| 環境の例 | int | long | long long |
|---|---|---|---|
| 32bit Windows(LLP64系) | 32b | 32b | 64b |
| 64bit Windows(LLP64系) | 32b | 32b | 64b |
| Linux 64bit (LP64系) | 32b | 64b | 64b |
このように、Windows(LLP64)ではlongは32ビットのままですが、多くのUNIX系64bit環境(LP64)ではlongは64ビットになります。
そのため、
- Windowsでの
atolは「32ビット整数変換」とほぼ同じ - Linux 64bitでの
atolは「64ビット整数変換」に近い
という違いが生じます。
#include <stdio.h>
#include <limits.h>
int main(void)
{
printf("sizeof(int) = %zu bytes\n", sizeof(int));
printf("sizeof(long) = %zu bytes\n", sizeof(long));
printf("LONG_MAX = %ld\n", LONG_MAX);
printf("LONG_MIN = %ld\n", LONG_MIN);
return 0;
}
sizeof(int) = 4 bytes
sizeof(long) = 8 bytes
LONG_MAX = 9223372036854775807
LONG_MIN = -9223372036854775808
上記のような出力になれば、その環境ではlongは64ビットであることが分かります。
atol使用時に確認すべきポイント
atolを使う前に、次の点を確認しておくと安全です。
- ターゲット環境でのlongのサイズ
sizeof(long)やLONG_MAXをチェックして、扱える範囲を把握しておきます。 - 扱いたい値がlongの範囲に収まるか
例えばID値やタイムスタンプなど、将来的に増大する可能性のある値を扱う場合、32ビットのlongでは足りないケースもあります。その場合はlong longやint64_tなど別の型を検討します。 - オーバーフローを検出する必要があるか
セキュリティや信頼性が重要な場面では、atolだけに頼らずstrtolやstrtollを使い、エラーや範囲外を検出する実装が求められます。
atoi・atolの注意点と代替関数
atoi・atolの戻り値でエラーを判定できない問題
atoiとatolの最大の問題点は、戻り値からエラーを判定できないことです。
- 不正な文字列
"abc"を変換すると0を返すことが多い - 正しい文字列
"0"も0を返す
結果として、「変換失敗による0」なのか「本来の値としての0」なのかを見分けられません。
int v1 = atoi("0"); /* 0 (正しい0) */
int v2 = atoi("abc"); /* 0 (変換失敗だが判別不能) */
この曖昧さが、入力値検証やエラー処理を困難にします。
不正な文字列・空文字列を渡した場合のリスク
atoi/atolに不正な文字列や空文字列を渡したときの挙動は、仕様上は次のように定義されています。
- 先頭に変換できる部分がなければ0を返す
(ただし、未定義動作とされるケースもあり、実用上も危険です)
例を見てみます。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const char *s1 = ""; /* 空文字列 */
const char *s2 = " "; /* 空白だけ */
const char *s3 = "xyz123"; /* 先頭が数字でない */
int v1 = atoi(s1);
int v2 = atoi(s2);
int v3 = atoi(s3);
printf("\"%s\" → %d\n", s1, v1);
printf("\"%s\" → %d\n", s2, v2);
printf("\"%s\" → %d\n", s3, v3);
return 0;
}
"" → 0
" " → 0
"xyz123" → 0
これらはすべて不正な入力ですが、戻り値だけを見ると0であり、正常な0との区別がつきません。
入力に不正な値が混入する可能性がある実環境では、atoi・atolをそのまま入力検証に使うのは非常に危険です。
入力値検証にatoi・atolを使うべきでない理由
以上のような理由から、セキュリティや信頼性が求められるコードで、atoi・atolを入力値検証に用いるべきではありません。
やってはいけない例を含んだコードを見てみます。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("使用法: %s <年齢>\n", argv[0]);
return 1;
}
/* 悪い例: atoiで年齢を変換し、そのまま使っている */
int age = atoi(argv[1]);
if (age < 0) {
printf("年齢が不正です\n");
return 1;
}
printf("あなたは%d歳です。\n", age);
return 0;
}
このプログラムでは、例えば次のような入力が問題になります。
./a.out abc→age = 0と解釈され、「0歳」と表示されてしまう./a.out ""→ 同様に0になる
このように明らかに不正な入力なのに、正常に見える動作をしてしまうことが、バグやセキュリティホールの原因になります。
strtolでの安全な文字列→数値変換
標準Cライブラリには、より安全に文字列を数値に変換するための関数strtolが用意されています。
long strtol(const char *restrict nptr, char **restrict endptr, int base);
strtolの主なポイントは次のとおりです。
- 変換がどこまで成功したかを
endptrを通じて知ることができる - 基数(10進、16進など)を
baseで指定できる - オーバーフローやアンダーフローの際に
errnoや返り値で検出可能

このようにstrtolはエラー検出や範囲チェックに必要な情報を返してくれるため、実務的にはstrtolの使用が推奨されます。
エラー処理と範囲チェックの実装例
strtolを用いて、安全な文字列→int変換を行うサンプルを示します。
ここでは、
- 文字列が完全に整数であることを確認
- intの範囲内であることを確認
- エラー時には適切なメッセージを表示
という流れを実装します。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
/* 文字列を安全にintへ変換する関数の例 */
int parse_int(const char *str, int *out_value)
{
char *endptr;
long val;
/* errnoを0にリセットしておく */
errno = 0;
/* 基数10で変換 */
val = strtol(str, &endptr, 10);
/* 1. 変換が全く行われなかった場合(先頭が数字でない) */
if (str == endptr) {
fprintf(stderr, "エラー: 数字が含まれていません(\"%s\")\n", str);
return -1;
}
/* 2. オーバーフロー/アンダーフローの検出 */
if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))) {
fprintf(stderr, "エラー: 値が範囲外です(\"%s\")\n", str);
return -1;
}
/* 3. intの範囲チェック */
if (val > INT_MAX || val < INT_MIN) {
fprintf(stderr, "エラー: intの範囲外です(\"%s\")\n", str);
return -1;
}
/* 4. 余分な文字がないか確認(今回は厳密にチェックする) */
if (*endptr != '\0') {
fprintf(stderr, "エラー: 不正な文字が含まれています(\"%s\")\n", str);
return -1;
}
/* ここまで来れば安全にintへキャストできる */
*out_value = (int)val;
return 0; /* 成功 */
}
int main(void)
{
const char *tests[] = {
"123",
"-42",
"0",
"abc",
"123abc",
"9999999999999999999999", /* きわめて大きな数 */
"",
" 100",
"2147483648", /* INT_MAX + 1 を想定 */
};
int value;
size_t i;
for (i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) {
printf("入力: \"%s\"\n", tests[i]);
if (parse_int(tests[i], &value) == 0) {
printf(" → 変換成功: %d\n", value);
} else {
printf(" → 変換失敗\n");
}
puts("");
}
return 0;
}
入力: "123"
→ 変換成功: 123
入力: "-42"
→ 変換成功: -42
入力: "0"
→ 変換成功: 0
入力: "abc"
エラー: 数字が含まれていません("abc")
→ 変換失敗
入力: "123abc"
エラー: 不正な文字が含まれています("123abc")
→ 変換失敗
入力: "9999999999999999999999"
エラー: 値が範囲外です("9999999999999999999999")
→ 変換失敗
入力: ""
エラー: 数字が含まれていません("")
→ 変換失敗
入力: " 100"
エラー: 数字が含まれていません(" 100")
→ 変換失敗
入力: "2147483648"
エラー: intの範囲外です("2147483648")
→ 変換失敗
この例では、あえて" 100"をエラー扱いにしています。
これは「入力は空白を含まない純粋な数値文字列であるべき」というルールを想定したためです。
もし" 100"も許容したければ、strの先頭の空白を呼び出し側でトリムするか、endptrのチェック条件を「余分な文字があっても空白なら許容する」などに変更することができます。
重要なポイントは、strtolを使うことで、どのような入力が不正なのかを細かく判定できるという点です。
atoi・atolでは実現しづらい厳密なエラー処理が可能になります。
まとめ
atoi・atolは、C言語で手軽に文字列を整数(int/long)に変換する関数ですが、戻り値だけではエラーを判定できず、オーバーフロー時の挙動も未定義という問題を抱えています。
学習用途や一時的なツールであれば便利ですが、入力値検証や本番コードではstrtolなどの安全な代替関数を使い、エラー判定と範囲チェックを明示的に行うことが重要です。
用途に応じて、手軽さを取るか安全性を取るかを意識し、適切な関数を選んで実装するようにしてください。
