C言語でキーボード入力や設定ファイルなどから数値を扱いたいとき、多くの場合はまず文字列としてデータを受け取ります。
そのままでは計算に使えないため、整数型に変換する必要があります。
本記事では、標準関数atoi/atol/atollを使って「数字の文字列」をintやlongなどの整数に変換する方法を、C言語初心者の方にも分かりやすいように詳しく解説します。
atoi/atol/atollとは
文字列を整数に変換する標準関数
C言語には、数字が並んだ文字列を整数値に変換するための標準関数がいくつか用意されています。
その代表的なものがatoi、atol、atollです。
これらの関数は、C標準ライブラリのstdlib.hで宣言されており、主な役割は次の通りです。
- 文字列(例:”12345″)を
- 対応する整数型(例:intやlong)の値に変換する
つまり、例えば“123”という文字列を数値の123として扱えるようにするための関数です。
atoi/atol/atollの違い
3つの関数の違いは、返り値の型(扱える整数の大きさ)だけです。
機能や動作の基本は同じです。
以下に違いを表でまとめます。
| 関数名 | 返り値の型 | 主な用途 |
|---|---|---|
| atoi | int | 比較的小さな整数の変換 |
| atol | long | intより大きな範囲の整数を扱いたい場合 |
| atoll | long long | さらに大きな整数(64bitなど)を扱いたい場合 |
どの型を使うかは、プログラムで扱いたい数値の最大値・最小値に応じて選びます。
例えば、ID番号や配列の添字などそこまで大きくならない値ならatoiを、大きな金額や秒数などかなり大きな数値を扱うならatolやatollを選ぶイメージです。
atoiの基本的な使い方
ヘッダファイルと関数プロトタイプ
atoiを使うには、ソースコードの先頭でstdlib.hをインクルードします。
関数プロトタイプは標準で次のように定義されています。
/* atoi, atol, atollはstdlib.hで宣言されています */
#include <stdlib.h>
/* 関数プロトタイプのイメージ */
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
重要なポイントは、引数のconst char *nptrが「変換したい文字列の先頭アドレス」であることです。
C言語では、通常の文字列リテラル"123"も、char配列も、この引数にそのまま渡せます。
簡単なサンプルコードと戻り値の確認
まずはatoiの動きをイメージするために、非常にシンプルな例を見てみます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* 数字の文字列を用意します */
const char *str1 = "123";
const char *str2 = "-45";
const char *str3 = "+78";
/* atoiでint型に変換します */
int n1 = atoi(str1);
int n2 = atoi(str2);
int n3 = atoi(str3);
/* 結果を表示して確認します */
printf("str1 = \"%s\" -> n1 = %d\n", str1, n1);
printf("str2 = \"%s\" -> n2 = %d\n", str2, n2);
printf("str3 = \"%s\" -> n3 = %d\n", str3, n3);
return 0;
}
str1 = "123" -> n1 = 123
str2 = "-45" -> n2 = -45
str3 = "+78" -> n3 = 78
この例から分かるように、数字の文字列をそのままintの値として扱えるようになることが確認できます。
また、先頭に+や-が付いていても、正しく符号付き整数として解釈されます。
先頭の空白や符号(+/-)の扱い
atoi系関数は、先頭の空白文字や符号をある程度自動的に無視・解釈してくれるという特徴があります。
- 先頭の空白(スペース、タブなど)は無視される
- 次に出てくる
+または-は、符号として扱われる - その後に続く連続した数字を整数として読み取る
これを確認するサンプルを見てみます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* 先頭にスペースやタブが含まれる文字列 */
const char *str1 = " 256"; /* 半角スペースが3つ */
const char *str2 = "\t\n -99"; /* タブ、改行、スペースのあとに-99 */
const char *str3 = " +42abc"; /* 数字の後ろに文字が続く */
int n1 = atoi(str1);
int n2 = atoi(str2);
int n3 = atoi(str3);
printf("str1 = \"%s\" -> %d\n", str1, n1);
printf("str2 = \"%s\" -> %d\n", str2, n2);
printf("str3 = \"%s\" -> %d\n", str3, n3);
return 0;
}
str1 = " 256" -> 256
str2 = "
-99" -> -99
str3 = " +42abc" -> 42
ここでのポイントは、数字の前の空白や改行、タブなどは読み飛ばされること、そして+42abcのように数字の後ろに文字が続いていても、数字の部分だけが変換されることです。
数字以外の文字が含まれる場合の変換結果
数字以外の文字が途中にある場合、そこで変換はストップします。
先ほどの" +42abc"の例では、+42までは正しい整数として読み取れるため、結果は42になります。
一方、先頭から見て数字が1文字も出てこない場合は、結果は0になります。
この挙動を確認してみます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *s1 = "123abc"; /* 先頭は数字 */
const char *s2 = "abc123"; /* 先頭は英字 */
const char *s3 = " xyz"; /* 空白の後ろも英字のみ */
int n1 = atoi(s1);
int n2 = atoi(s2);
int n3 = atoi(s3);
printf("s1 = \"%s\" -> %d\n", s1, n1);
printf("s2 = \"%s\" -> %d\n", s2, n2);
printf("s3 = \"%s\" -> %d\n", s3, n3);
return 0;
}
実行結果の一例は次の通りです。
s1 = "123abc" -> 123
s2 = "abc123" -> 0
s3 = " xyz" -> 0
先頭から読んで、空白や符号のあとに数字がなければ0になることに注意が必要です。
特に変換に失敗した場合と、本当に0という値が入力された場合が区別できないという問題につながります。
この点については後半の「注意点」で詳しく説明します。
atol・atollで扱える範囲を広げる
long型・long long型の数値範囲と用途
atoiはint型の範囲でしか値を表現できません。
環境によって異なりますが、多くの環境でintは32bitで、だいたい-2,147,483,648から2,147,483,647までです。
より大きな値を扱いたいときは、longやlong longといった、より広い範囲を持つ整数型を使います。
それに対応するのがatol、atollです。
用途のイメージとしては次のようになります。
atoi(int)
配列のインデックス、年齢、個数など、それほど大きくならない値atol(long)
ファイルサイズ、タイムスタンプ、ID値など、やや大きめの値atoll(long long)
非常に大きなID、ナノ秒単位の時間、巨大なカウンタなど、大きくなりうる値
どれを使うか迷った場合は、扱う値の最大値を見積もり、余裕を持って型を選ぶと良いです。
大きな数値を変換するサンプルコード
atolとatollを使って、より大きな数値を変換する例を示します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *large1 = "2147483647"; /* 32bit intの最大値くらい */
const char *large2 = "2147483648"; /* intの上限を超える可能性がある値 */
const char *large3 = "9223372036854775807"; /* long longの最大値くらい */
long l1 = atol(large1);
long l2 = atol(large2);
long long ll3 = atoll(large3);
printf("large1 = \"%s\" -> long = %ld\n", large1, l1);
printf("large2 = \"%s\" -> long = %ld\n", large2, l2);
printf("large3 = \"%s\" -> long long = %lld\n", large3, ll3);
return 0;
}
実行結果の一例です(環境によって表示は変わる可能性があります)。
large1 = "2147483647" -> long = 2147483647
large2 = "2147483648" -> long = 2147483648
large3 = "9223372036854775807" -> long long = 9223372036854775807
このようにatolやatollを使うと、intでは表現しきれない大きな数値を扱えるようになります。
ただし、この挙動は環境の型サイズに依存しますので、次の節で解説する点に注意が必要です。
32bit環境・64bit環境での型サイズの違いに注意
C言語では、intやlong、long longが必ずしも同じビット数とは限りません。
特にlong型は、OSやコンパイラ、アーキテクチャによってサイズが変わることがあります。
代表的な例を表にまとめます。
| 環境の例 | intのサイズ | longのサイズ | long longのサイズ |
|---|---|---|---|
| 32bit多くの環境 | 32bit | 32bit | 64bit |
| 64bit Linux/GCCなど | 32bit | 64bit | 64bit |
| 64bit Windows/MSVCなど | 32bit | 32bit | 64bit |
このように、64bit環境でもlongが32bitのままの実装もあれば、longが64bitの実装も存在します。
そのため、「longなら64bitのはず」と決めつけないことが重要です。
大きな数値を安全に扱いたい場合、次のように長さを前提として型を選ぶのが実用的です。
- 32bit以上の整数が必要なら
intやlong、環境に応じたlongを確認する - 必ず64bit整数が欲しいなら、
long longとatollを組み合わせる - さらに厳密にビット幅を固定したいなら、
stdint.hにあるint64_tなどを使い、変換にはstrtollなどを利用する
同じソースコードでも、ビルドする環境によってlongのサイズが変わる可能性があるため、移植性を考えるときには特に注意してください。
atoi系関数を使うときの注意点
ここまでatoi、atol、atollの便利な側面を見てきましたが、実は実務ではあまり推奨されない場面も多いです。
その理由は、エラー処理がしづらいことにあります。
変換失敗時の判定ができない問題
atoi系関数には、「変換に失敗したかどうか」を判定する手段が用意されていません。
例えば、次の2つのケースを考えます。
- 入力文字列が
"0"で、正しく0に変換できた場合 - 入力文字列が
"abc"で、数字が1文字もなく、結果として0が返された場合
どちらの場合もatoiの戻り値は0になります。
戻り値だけを見ても、成功か失敗か判断できません。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *ok = "0";
const char *ng = "abc";
int n_ok = atoi(ok);
int n_ng = atoi(ng);
printf("ok = \"%s\" -> %d\n", ok, n_ok);
printf("ng = \"%s\" -> %d\n", ng, n_ng);
/* 出力はどちらも0になり、成功か失敗か区別できません */
return 0;
}
このような理由から、ユーザー入力や外部からのデータを厳密にチェックしたい場合には、atoi系よりもstrtol系の関数が推奨されます。
オーバーフロー・アンダーフロー時の動作
もうひとつの問題は、オーバーフローやアンダーフローが起きたときの挙動が未定義であることです。
- オーバーフロー: 変換結果が型の最大値を超える場合
- アンダーフロー: 変換結果が型の最小値より小さくなる場合
C標準では、atoi系の関数でこれらが起きたときにどういう値が返るか、あるいは何が起きるかが保証されていません。
環境によっては
- 最大値や最小値に張り付く
- 意味の分からない値になる
- 実装依存の挙動をする
などの可能性があります。
例えば、32bitのintで"999999999999"のような非常に大きな値をatoiに渡すと、結果が信頼できない状態になります。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *too_large = "999999999999"; /* intでは入りきらない可能性が高い */
int n = atoi(too_large);
printf("too_large = \"%s\" -> %d (結果は未定義)\n", too_large, n);
return 0;
}
このように、安全性や信頼性が重要なプログラムでは、atoi系関数の使用は慎重に検討する必要があります。
安全に変換したい場合はstrtol/strtollの利用も検討
厳密なエラー判定やオーバーフロー判定を行いたい場合は、atoi/atol/atollではなくstrtolやstrtollを使うことが一般的です。
strtol系の関数には次のような特徴があります。
- 変換に成功した部分の末尾位置を、ポインタを通して返してくれる
- 範囲外の値が来た場合、
errnoや戻り値でオーバーフロー・アンダーフローを検出できる - 基数(10進数、16進数など)を指定できる
ここでは詳細な使い方の解説は省きますが、安全に文字列を整数に変換したい場合、まず候補に挙がるのはstrtol/strtollであるという点を覚えておくと良いです。
簡単な比較を文章でまとめると、次のようなイメージになります。
atoi/atol/atoll
記述は簡単だが、エラー検出やオーバーフロー検出ができない。テスト用や「多少失敗しても構わない」用途向き。strtol/strtoll
引数が多く少し複雑だが、エラー処理がしっかり書ける。ユーザー入力やファイル入力の解析など、本番コード向き。
そのため、C言語を学ぶ初期段階ではatoiで文字列から整数に変換する感覚をつかみ、慣れてきたらstrtol/strtollにステップアップするという流れがおすすめです。
まとめ
本記事では、C言語の文字列を整数に変換する標準関数atoi/atol/atollの基本について解説しました。
これらの関数は、空白や符号を自動的に処理し、数字部分だけを手軽にintやlongなどへ変換できる便利な道具です。
一方で、変換失敗やオーバーフローを判定できないという欠点もあるため、厳密な入力チェックが必要な場面ではstrtolやstrtollの利用を検討してください。
用途に応じて関数と型を選び、安全で読みやすいCプログラムを書いていきましょう。
