閉じる

【C言語】atoiの正しい使い方|文字列を数値に変換する

C言語で文字列を数値に変換するとき、もっともよく見かける関数がatoiです。

しかし、標準ライブラリだからといって安全とは限りません。

使い方を誤ると、オーバーフローや未定義動作につながり、バグやセキュリティホールの原因になります。

この記事では、atoiの基本仕様から、安全な代替APIへの置き換え方まで、丁寧に解説します。

atoiとは|C言語で文字列をint型に変換する関数

atoiの基本仕様と返り値

atoiは、C標準ライブラリで提供される文字列をint型に変換する関数です。

関数プロトタイプは次のとおりです。

C言語
int atoi(const char *nptr);

引数nptrには、数値を表す文字列へのポインタを渡します。

返り値は、可能な範囲で整数に変換したint型の値です。

代表的な変換結果を、まずイメージで整理しておきます。

この図の内容を、もう少し正確に文章で説明します。

  1. 先頭に空白文字(スペースやタブなど)があれば読み飛ばします。
  2. 続いて‘+’または‘-‘の符号があれば読み取ります。
  3. その後、連続する数字文字(‘0’〜’9’)を読み取り、その部分だけを整数に変換します。
  4. 数字が一つも読めなかった場合、返り値は0になります。

ここで重要なのは、途中に数値以外の文字があっても、先頭から連続した数字までを変換するという点です。

たとえば"123abc"123に変換されます。

atoiと標準ヘッダstdlib.hの関係

atoi標準ヘッダ<stdlib.h>で宣言されている関数です。

利用する際は、必ずヘッダをインクルードします。

C言語
#include <stdio.h>
#include <stdlib.h>  // atoiを使うときに必要

int main(void) {
    const char *text = "123";
    int value = atoi(text);

    printf("text = %s, value = %d\n", text, value);
    return 0;
}

このように正しいヘッダをインクルードしておかないと、コンパイル時に警告が出たり、古いコンパイラ環境では暗黙の宣言による未定義動作につながる可能性があります。

atoiが使われる代表的な場面

atoiは「外部から受け取った文字列を整数に変換したい場面」でよく使われます。

代表的には次のようなケースがあります。

  • コマンドライン引数argv[]で渡された数値をintに変換するとき
  • 設定ファイルや環境変数から読み取った数値文字列を変換するとき
  • ユーザー入力をfgetsなどで読み取り、それを整数として扱いたいとき

ただし、後述のとおりatoiはエラー検出機構に乏しいため、そのまま業務系やセキュリティが重要なコードに使うのは危険です。

学習用や簡易ツールでは使われますが、実務ではstrtolなどへの置き換えが推奨されます。

atoiの正しい使い方と基本的な書き方

atoiの書式とサンプルコード

基本的な使い方はシンプルで、数値を表す文字列をそのまま渡すだけです。

C言語
#include <stdio.h>
#include <stdlib.h>  // atoiの宣言

int main(void) {
    // 数値を表す文字列
    const char *s1 = "123";
    const char *s2 = "-456";
    const char *s3 = "789abc";  // 後ろに文字が続く例

    // 文字列をint型に変換
    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 = "123" -> 123
s2 = "-456" -> -456
s3 = "789abc" -> 789

先頭から連続する数字部分だけが変換されることが確認できます。

先頭の空白や符号の扱い

atoi先頭の空白文字を読み飛ばし、続く符号'+''-'を解釈します。

サンプルコードで、空白や符号の挙動を確認してみます。

C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *a = "   42";     // 先頭にスペース
    const char *b = "\t\n+17";   // タブや改行も空白扱い
    const char *c = "   -99end"; // 符号付きで後ろに文字が続く

    int na = atoi(a);
    int nb = atoi(b);
    int nc = atoi(c);

    printf("a: \"%s\" -> %d\n", a, na);
    printf("b: \"%s\" -> %d\n", b, nb);
    printf("c: \"%s\" -> %d\n", c, nc);

    return 0;
}
実行結果
a: "   42" -> 42
b: "	
+17" -> 17
c: "   -99end" -> -99

ここから分かるように、atoiは「先頭の空白を無視しつつ、符号と数字部分だけを読む」動作をします。

数値以外の文字が含まれる場合の挙動

数字以外の文字が途中に現れたら、そこで変換が打ち切られます

その時点までに読んだ数字をもとに値が決まります。

C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *s1 = "100abc200";
    const char *s2 = "007-89";
    const char *s3 = "+12.34";   // 小数点が登場する例

    int n1 = atoi(s1);  // "100" まで
    int n2 = atoi(s2);  // "007" まで
    int n3 = atoi(s3);  // "12" まで (小数としては扱われない)

    printf("\"%s\" -> %d\n", s1, n1);
    printf("\"%s\" -> %d\n", s2, n2);
    printf("\"%s\" -> %d\n", s3, n3);

    return 0;
}
実行結果
"100abc200" -> 100
"007-89" -> 7
"+12.34" -> 12

このように、先頭から見て「どこまでが1つの整数なのか」を自動的に判断してくれますが、裏を返せば途中にゴミが混ざっていても検知できないという問題があります。

0が返る場合の注意点

atoiの返り値が0だからといって、必ずしも「入力が”0″だった」とは限りません

次の3つのパターンを比較してみます。

C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *s1 = "0";
    const char *s2 = "000";
    const char *s3 = "abc";   // 数字が1つもない
    const char *s4 = "";      // 空文字列

    int n1 = atoi(s1);
    int n2 = atoi(s2);
    int n3 = atoi(s3);
    int n4 = atoi(s4);

    printf("\"%s\" -> %d\n", s1, n1);
    printf("\"%s\" -> %d\n", s2, n2);
    printf("\"%s\" -> %d\n", s3, n3);
    printf("\"%s\" -> %d\n", s4, n4);

    return 0;
}
実行結果
"0" -> 0
"000" -> 0
"abc" -> 0
"" -> 0

数字が1つも読めなかったときも、”0″を変換したときも、同じ0が返るため、「入力が不正かどうか」を返り値だけでは判別できません

これがatoi最大の問題点の1つです。

atoi使用時の注意点と落とし穴

整数オーバーフローのリスク

atoiは範囲外の値が与えられたときの扱いを定義していません

つまり、オーバーフローやアンダーフローが起きると未定義動作です。

実際に大きな数値を与えた場合、環境によっては値が丸められたり、負の値になったり、同じ入力でも挙動が変わることがあります。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int main(void) {
    const char *big = "9999999999";   // 通常のintでは収まらない可能性が高い

    int x = atoi(big);

    printf("INT_MAX = %d\n", INT_MAX);
    printf("input = %s, atoi(input) = %d\n", big, x);

    return 0;
}
実行結果
INT_MAX = 2147483647
input = 9999999999, atoi(input) = (環境依存の未定義な値)

ここで表示される値は実行環境やコンパイラの最適化によって変わる可能性があり、信頼できません

この問題を避けるためには、strtolなどオーバーフローを検出できる関数を使う必要があります。

想定外の文字列入力での未定義動作

atoiの仕様は「数値に変換できるような文字列」が前提です。

実は、標準規格上は引数に与える文字列がchar*として有効であれば、文字内容そのものが原因で未定義動作になることはありませんが、数値の範囲外やマルチバイト文字などを含めたときの扱いは実装依存になりがちです。

問題になりやすいパターンを整理します。

  • 半角以外(全角数字や日本語)が含まれている
  • 文字コードが想定外(UTF-8の途中バイトなど)
  • 非ASCII環境での符号や空白の扱いが異なる
C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // ソースコードの文字コードや環境によって結果が変わる可能性がある例
    const char *s1 = "123";   // 全角数字 (UTF-8やShift_JISなど)
    const char *s2 = "3a5";    // 途中にASCII以外が混在

    int n1 = atoi(s1);
    int n2 = atoi(s2);

    printf("\"%s\" -> %d\n", s1, n1);
    printf("\"%s\" -> %d\n", s2, n2);

    return 0;
}
実行結果
(実行環境によって結果が変わる、もしくは0になるなど予測困難)

atoiは「ASCIIの数字と符号、空白」を前提に設計された古典的な関数であり、マルチバイト文字やロケール依存の文字処理までは考慮されていません

ロケールや数値形式(16進数や先頭0)の扱い

atoi常に10進数として解釈します。

つまり、次のような文字列の扱いは以下のようになります。

  • "010" → 10として扱う(8進数ではない)
  • "0x10" → 0として扱い、その後の'x'で打ち切られる
  • "FF" → 数字がないので0
C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *oct = "010";    // 8進数っぽく見える
    const char *hex1 = "0x10";  // 16進数リテラル風
    const char *hex2 = "FF";    // 16進数としては255だが…

    int n_oct  = atoi(oct);
    int n_hex1 = atoi(hex1);
    int n_hex2 = atoi(hex2);

    printf("\"%s\" -> %d\n", oct,  n_oct);
    printf("\"%s\" -> %d\n", hex1, n_hex1);
    printf("\"%s\" -> %d\n", hex2, n_hex2);

    return 0;
}
実行結果
"010" -> 10
"0x10" -> 0
"FF" -> 0

atoiは「C言語の数値リテラルの規則」をそのまま真似しているわけではないことが分かります。

16進数や8進数を扱いたい場合はstrtolで基数を指定する必要があります。

また、ロケールの影響についても注意が必要です。

atoiは基本的にCロケール前提ですが、環境によってはisspaceなどロケール影響下の関数と同様の判定を内部で使うことがあります。

数値の区切り文字(カンマなど)や小数点の記号をロケールに応じて解釈してくれるわけではありません

セキュリティ上の注意点

atoiはエラー検出ができない、範囲外でも未定義動作、とセキュリティ的に弱い要素を多く持っています

特に次のような場面では直接atoiを使うべきではありません

  • ネットワークから受信した文字列をそのまま変換してサイズやポート番号に使う
  • Webフォームや外部APIからの入力をそのまま数値として扱う
  • 認証、認可、料金計算など、セキュリティやお金に関わる処理

安全な実装では、必ず「文字列の形式チェック」と「変換結果の範囲チェック」を行うべきです。

これをサポートしてくれるのが、次に紹介するstrtolやその他の変換関数です。

atoiの代替関数と安全な文字列変換

atoiとstrtolの違いと使い分け

strtolは、atoiの上位互換ともいえる、より強力で安全な変換関数です。

プロトタイプは次のとおりです。

C言語
long strtol(const char *restrict nptr, char **restrict endptr, int base);

atoiとstrtolの主な違いを表にまとめます。

項目atoistrtol
ヘッダ<stdlib.h><stdlib.h>
返り値の型intlong
エラー検出できないerrnoendptr で可能
オーバーフロー検出できない(未定義動作)ERANGELONG_MAX/LONG_MIN
数値基数常に10進数2〜36、または0(自動判定)
変換終了位置の取得不可endptr で取得可能

実務で「文字列を整数に変換する」必要があるなら、まずはstrtolの使用を検討すべきです。

その上で、返り値がintに収まるかどうかをチェックし、必要に応じてキャストします。

strtolを使った安全な変換例

ここでは、「10進数のみ受け付ける、安全なint変換」の一例を示します。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>

// 文字列を安全にintに変換する関数例
bool safe_atoi(const char *str, int *out_value) {
    char *endptr;
    long val;

    if (str == NULL || *str == '\0') {
        // NULLや空文字列は不正とみなす
        return false;
    }

    errno = 0;  // errnoをリセット
    val = strtol(str, &endptr, 10);  // 10進数として解釈

    // 1) 変換でエラーが起きていないか確認
    if (errno == ERANGE) {
        // オーバーフローまたはアンダーフロー
        return false;
    }

    // 2) 一文字も変換されなかった(数字がなかった)場合
    if (str == endptr) {
        return false;
    }

    // 3) 末尾に余分な文字がないかチェック(許容するかは要件次第)
    if (*endptr != '\0') {
        // ここでは「完全に数値だけ」を要求することにする
        return false;
    }

    // 4) longの結果がintに収まるか確認
    if (val < INT_MIN || val > INT_MAX) {
        return false;
    }

    *out_value = (int)val;
    return true;
}

int main(void) {
    const char *tests[] = {
        "123",
        "  -45",
        "99999999999999999999", // オーバーフロー
        "abc",
        "12abc",
        "",
        NULL
    };

    for (int i = 0; i < (int)(sizeof(tests) / sizeof(tests[0])); i++) {
        const char *s = tests[i];
        int value;

        if (s == NULL) {
            printf("tests[%d]: NULL (skipped)\n", i);
            continue;
        }

        if (safe_atoi(s, &value)) {
            printf("\"%s\" -> OK: %d\n", s, value);
        } else {
            printf("\"%s\" -> NG (invalid or out of range)\n", s);
        }
    }

    return 0;
}
実行結果
"123" -> OK: 123
"  -45" -> OK: -45
"99999999999999999999" -> NG (invalid or out of range)
"abc" -> NG (invalid or out of range)
"12abc" -> NG (invalid or out of range)
"" -> NG (invalid or out of range)
tests[6]: NULL (skipped)

このようなラッパ関数を用意しておくと、atoiより安全で意図の明確な変換が行えます。

C11のstrtoimaxなど拡張関数の紹介

C99以降では固定幅整数型が導入され、それに対応する変換関数も用意されています。

特に大きな整数や環境依存性の少ないコードを書きたいときには、strtoimaxなどの関数が有用です。

代表的な関数は次のとおりです。

関数名対応する型ヘッダ
strtoimaxintmax_t<inttypes.h>
strtoumaxuintmax_t<inttypes.h>
strtolllong long<stdlib.h>
strtoullunsigned long long<stdlib.h>

intmax_tは「その環境で扱える最大の整数型」であり、ポータビリティを重視したコードでは有力な選択肢になります。

C言語
#include <stdio.h>
#include <inttypes.h>  // intmax_t, strtoimax
#include <errno.h>

int main(void) {
    const char *s = "9223372036854775807"; // 2^63 - 1 相当の大きな値
    char *endptr;
    errno = 0;

    intmax_t v = strtoimax(s, &endptr, 10);

    if (errno == ERANGE) {
        printf("range error\n");
        return 1;
    }
    if (s == endptr) {
        printf("no digits were found\n");
        return 1;
    }
    if (*endptr != '\0') {
        printf("extra characters: \"%s\"\n", endptr);
        return 1;
    }

    printf("input: %s\n", s);
    printf("intmax_t value: %" PRIdMAX "\n", v);

    return 0;
}
実行結果
input: 9223372036854775807
intmax_t value: 9223372036854775807

大きな数値を安全に扱いたい場合には、まずstrtoimaxstrtollを検討すると良いでしょう。

既存コードのatoiを安全な関数に置き換えるポイント

すでに大規模なコードベースでatoiが大量に使われている場合、一気にすべてを書き換えるのは現実的ではありません

そこで、徐々に、安全性の高い関数へ置き換えていく戦略が重要になります。

置き換え時のポイントを、いくつか具体的に挙げます。

1. ラッパ関数を用意する

前述のsafe_atoiのように、変換ロジックをひとつの関数に集約しておくと、仕様変更やバグ修正がしやすくなります。

最初は簡易版でも構わないので、「atoiを直叩きしない」という習慣をつけることが重要です。

2. 呼び出し箇所ごとに要件を確認する

すべてのatoi呼び出しが、同じ要件を満たす必要があるわけではありません

  • 「先頭に空白があってもよいか」
  • 「末尾にゴミ文字があってもよいか」
  • 「0を特別扱いしているか」
  • 「負数を許可するか」

これらを洗い出し、状況に応じてstrtolstrtoulなど適切な関数を選ぶことが大切です。

3. テストを強化する

atoiから別の関数に置き換えると、これまで「たまたま動いていた」境界ケースで挙動が変わることがあります。

そのため、置き換え前後で次のようなテストを用意すると安全です。

  • 正常な数値(正・負・0)
  • 非数値文字列(空文字、英字のみ、数値+文字の混在など)
  • 上限・下限近辺の値(オーバーフローの直前)
  • 先頭・末尾の空白や制御文字

4. ルール化して新規のatoi使用を防ぐ

プロジェクト内のコーディング規約として、「新規コードではatoiを使用しない」ことを明記し、コードレビューや静的解析ツールでのチェック対象にすると、長期的な品質向上につながります。

まとめ

atoiは「文字列をintに変換する」ための、シンプルで歴史ある関数ですが、エラー検出ができず、オーバーフローも未定義動作という大きな欠点があります。

学習用や簡単なツールで挙動を理解するには便利ですが、実務やセキュアな開発ではstrtolやstrtoimaxなど、安全な代替関数の利用を強く推奨します。

この記事で紹介したsafe_atoiのようなラッパ関数や、エラー処理・範囲チェックの考え方を取り入れることで、「文字列から数値への変換」が原因となるバグや脆弱性を大幅に減らすことができます。

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

URLをコピーしました!