C言語のatoi関数の使い方: 文字列を整数に変換する

140, 2020-12-22

目次

atoi関数の使い方

C言語には数字の書かれた文字列をint型の整数に変換する関数atoi()があります。
この記事ではatoi関数の使い方を解説します。

具体的には↓を見ていきます。

  • atoi関数の構造

  • atoi関数の使い方

  • 不正な引数

  • atoi()の代わりにstrtol()を使う

atoi関数の構造

atoi関数はstdlib.hをインクルードすると使うことが出来ます。

#include <stdlib.h>

atoi関数は↓のような作りになっています。

int atoi(const char *nptr);

atoi関数は1つの文字列の引数を取り、int型の返り値を返します。
atoi関数はスレッドセーフです。
atoi関数はエラーを検出しません。

nptr(第1引数)

nptrconst char *型の文字列です。
文字列は↓のようなものが該当します。

"this is string";
const char *s1 = "this is string";
char s2[] = "this is string";

返り値(int

atoi関数の返り値はintです。

atoi関数の使い方

atoi関数の使い方です。
atoi関数の第1引数に数字の書かれた文字列を渡すと、atoi関数は内部でその数字の書かれた文字列をパースし、int型の整数に変換します。
つまり↓のように使うことが出来ます。

#include <stdlib.h>
#include <stdio.h>

int
main(void) {
    int n = atoi("123");
    printf("%d\n", n);
    return 0;
}

↑のコードを実行すると結果は↓のようになります。

123

atoi関数の第1引数はconst char *なので、文字列リテラルの他にもconst char *char *型の変数を渡すことも出来ます。

#include <stdlib.h>
#include <stdio.h>

int
main(void) {
    const char *s1 = "123";
    int n1 = atoi(s1);
    printf("%d\n", n1);

    char s2[] = "456";
    int n2 = atoi(s2);
    printf("%d\n", n2);
    return 0;
}

↑のコードを実行すると↓のような結果になります。

123
456

不正な引数

atoi関数はエラーを検出しません。
つまり第1引数が不正だった場合に、atoi関数を使う側はその不正を知るすべがないということです。
不正な引数を渡した場合のatoi関数の挙動を見てみたいと思います。

NULLを渡した場合

atoi関数にNULLを渡した場合、atoi関数はSegmentation faultを起こす可能性があります。

#include <stdlib.h>
#include <stdio.h>

int
main(void) {
    int n = atoi(NULL);
    printf("%d\n", n);
    return 0;
}

↑のコードを実行すると↓のような結果になり、実行時エラーになります。

Segmentation fault

Valgrindなどのメモリチェックツールでチェックしてみると↓のような結果になります。

...
==6694== Invalid read of size 1
==6694==    at 0x4E75454: ____strtol_l_internal (strtol_l.c:293)
==6694==    by 0x4E70E8F: atoi (atoi.c:27)
==6694==    by 0x400503: main (in /tmp/a.out)
==6694==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
...
Segmentation fault

↑を見ると「Invalid read of size 1」と表示されているのがわかります。
つまりatoi関数は引数がNULLだった場合、NULLでも気にせずに読み込みに行ってるということになります。
引数がNULLかどうかのチェックはしていないようです。

数字じゃない文字列を渡した場合

atoi関数に数字ではない文字列を渡すとどうなるのでしょうか。

#include <stdlib.h>
#include <stdio.h>

int
main(void) {
    int n1 = atoi("abc");
    printf("%d\n", n1);

    int n2 = atoi("abc123");
    printf("%d\n", n2);

    int n3 = atoi("123abc");
    printf("%d\n", n3);

    int n4 = atoi("a1b2c");
    printf("%d\n", n4);
    return 0;
}

↑のコードを実行すると↓のような結果になります。

0
0
123
0

↑の結果を見ると文字列の先頭が数字でない場合は0を返しているのがわかります。
先頭が数字で始まっていて、途中から数字で無くなった場合は、それまでのパースの結果を返しています(123)。

atoi()の代わりにstrtol()を使う

atoi関数はエラーを検出しないのであまりセキュアな関数ではありません。
エラーを検出したい場合はstrtol関数の使用を検討しましょう。
strtol関数はC99以降に対応しているコンパイラで使うことが出来ます。

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int
main(void) {
    errno = 0;

    long int n = strtol("123456789123456789123456789123456789", NULL, 10);
    switch (errno) {
    case ERANGE: perror("strtol(). out of range"); break;
    }

    printf("%d\n", n);
    return 0;
}

↑のコードを実行すると↓のような結果になります。

strtol(). out of range: Numerical result out of range
-1

↑のようにstrtol関数は値が範囲外だった場合にerrnoERANGEをセットします。
ただしstrtol関数も引数がNULLだった場合と、引数が数字じゃなかった場合はエラーになりません。
そのためよりセキュアな関数を使いたい場合はラッパーを作って自作しましょう。

おわりに

C言語でatoi関数を使う機会はけっこう多いです。
しかしatoi関数はエラーを検出しないなど、いろいろとクセのある関数です。
場合によってはラッパーを作って対応しましょう。

C言語ってそういう関数多いよね

まぁ時代が時代だからね

やっぱラッパーか外部ライブラリを使うのが正義だな