閉じる

【C言語】 安全な数値変換 strtol,strtodでエラーを見抜く

数値を文字列から安全に取り出すのは初心者がつまずきやすいポイントです。

atoiやatofでは誤入力やオーバーフローを見逃しやすく、バグの温床になります。

本記事ではstrtolstrtodの正しい使い方とエラー検出の勘所を、実行例とともに丁寧に解説します。

C言語の数値変換(strtol,strtod)入門

何を解決するか

文字列から数値へ変換する場面では、以下のような問題が発生します。

入力の一部だけが数値で末尾にゴミが付いている、空文字や空白しかない、範囲外でオーバーフローする、符号や基数を間違う、といったケースです。

strtolとstrtodは、どこまで読めたかを示すポインタと、範囲エラー通知であるerrnoを用いて、これらを正確に検出できます

これが安全な入力検証の土台になります。

atoi/atofとの違い

最大の違いはエラー検出能力です。

atoiatofは失敗時の区別がつかず、末尾のゴミも無視されます。

一方strtolstrtodendptrerrnoで詳細に判断できます。

観点atoi/atofstrtol/strtod
エラー検出失敗を区別できない。0との区別不能endptrとerrnoで詳細判定
末尾の未変換無視して返すendptrで未変換位置がわかる
オーバーフロー未定義挙動または不明確errnoにERANGE、値は飽和
符号と空白受け付ける受け付ける
基数指定不可base指定可、0で自動判別

特に「0の入力」と「変換失敗で0が返る」区別がつかないのが致命的です。

これがatoi/atofを避けるべき最大理由です。

変換の流れ

基本パターンは「errno初期化→strtoX実行→endptrで未変換確認→errnoのERANGE確認→末尾にゴミがないか確認」です。

整数でも小数でも同じ流れで考えます。

  • 先頭の空白は自動でスキップされます。
  • 符号は省略可能で1つのみ有効です。
  • baseが0なら0始まりは8進、0x/0Xは16進、それ以外は10進が選ばれます。
  • errno == ERANGEなら範囲外です。strtolLONG_MAX/LONG_MINに張り付きます。strtodHUGE_VALなどになります。

エラー処理の要点

endptrで未変換をチェック

endptrが入力先頭と同じなら、数字として解釈できた文字が1つもないことを意味します。

さらに、最後まで読めているかを確認すると「末尾のゴミ」を見逃しません。

C言語
/* endptrの使い方デモ: 未変換や末尾のゴミを検出する */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>

static void demo(const char *s) {
    errno = 0;
    char *end = NULL;
    long v = strtol(s, &end, 10); /* 10進として解釈 */

    printf("input=\"%s\"\n", s);

    if (end == s) {
        /* 1文字も数字として認識されなかった */
        printf(" -> no digits found\n\n");
        return;
    }
    printf(" value=%ld, stopped at index %ld\n", v, (long)(end - s));

    /* 末尾に空白以外の文字が残っていないかを確認 */
    while (*end != '
/* endptrの使い方デモ: 未変換や末尾のゴミを検出する */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
static void demo(const char *s) {
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10); /* 10進として解釈 */
printf("input=\"%s\"\n", s);
if (end == s) {
/* 1文字も数字として認識されなかった */
printf(" -> no digits found\n\n");
return;
}
printf(" value=%ld, stopped at index %ld\n", v, (long)(end - s));
/* 末尾に空白以外の文字が残っていないかを確認 */
while (*end != '\0' && isspace((unsigned char)*end)) {
end++;
}
if (*end == '\0') {
printf(" -> OK: only whitespace remained after number\n\n");
} else {
printf(" -> NG: leftover=\"%s\"\n\n", end);
}
}
int main(void) {
demo("123abc");
demo("abc");
demo("   42 ");
return 0;
}
' && isspace((unsigned char)*end)) { end++; } if (*end == '
/* endptrの使い方デモ: 未変換や末尾のゴミを検出する */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
static void demo(const char *s) {
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10); /* 10進として解釈 */
printf("input=\"%s\"\n", s);
if (end == s) {
/* 1文字も数字として認識されなかった */
printf(" -> no digits found\n\n");
return;
}
printf(" value=%ld, stopped at index %ld\n", v, (long)(end - s));
/* 末尾に空白以外の文字が残っていないかを確認 */
while (*end != '\0' && isspace((unsigned char)*end)) {
end++;
}
if (*end == '\0') {
printf(" -> OK: only whitespace remained after number\n\n");
} else {
printf(" -> NG: leftover=\"%s\"\n\n", end);
}
}
int main(void) {
demo("123abc");
demo("abc");
demo("   42 ");
return 0;
}
') { printf(" -> OK: only whitespace remained after number\n\n"); } else { printf(" -> NG: leftover=\"%s\"\n\n", end); } } int main(void) { demo("123abc"); demo("abc"); demo(" 42 "); return 0; }
実行結果
input="123abc"
 value=123, stopped at index 3
 -> NG: leftover="abc"

input="abc"
 -> no digits found

input="   42 "
 value=42, stopped at index 6
 -> OK: only whitespace remained after number

実務では「最後まで読めたか」を必ず確認し、途中までしか読めない入力はエラー扱いにするのが安全です。

errnoとERANGEを確認

呼び出し前にerrno = 0を必ず行い、呼び出し後にerrno == ERANGEかを確認します。

範囲外の数値でオーバーフローやアンダーフローが発生した場合に検知できます。

C言語
/* 範囲エラー検出: strtol と strtod の ERANGE */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <math.h>

int main(void) {
    const char *s1 = "999999999999999999999999999999";
    errno = 0;
    char *end = NULL;
    long v = strtol(s1, &end, 10);
    printf("strtol overflow: input=%s\n", s1);
    printf(" endptr advanced? %s\n", end != s1 ? "yes" : "no");
    if (errno == ERANGE) {
        printf(" errno=ERANGE, value=%ld (clamped to LONG_MAX or LONG_MIN)\n", v);
    } else {
        printf(" errno=%d, value=%ld\n", errno, v);
    }

    errno = 0;
    const char *s2 = "1e309"; /* 2進64bitの倍精度でオーバーフロー */
    double d = strtod(s2, &end);
    printf("\nstrtod overflow: input=%s\n", s2);
    printf(" endptr advanced? %s\n", end != s2 ? "yes" : "no");
    if (errno == ERANGE) {
        printf(" errno=ERANGE, value=%f (HUGE_VAL with sign)\n", d);
    } else {
        printf(" errno=%d, value=%f\n", errno, d);
    }
    return 0;
}
実行結果
strtol overflow: input=999999999999999999999999999999
 endptr advanced? yes
 errno=ERANGE, value=9223372036854775807 (clamped to LONG_MAX or LONG_MIN)

strtod overflow: input=1e309
 endptr advanced? yes
 errno=ERANGE, value=inf (HUGE_VAL with sign)

strtodのアンダーフローではerrno==ERANGEかつ非常に小さい数、または0.0が返ることがあります。

空文字・数字なしの判定

空白は自動でスキップされますが、空文字のみや、符号のみは「数字なし」となりendptr == nptrになります。

これは入力エラーとみなすべきです。

C言語
/* 空文字・数字なしの判定例 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

static void check(const char *s) {
    errno = 0;
    char *end = NULL;
    long v = strtol(s, &end, 10);
    printf("input=\"%s\": ", s);
    if (end == s) {
        printf("no conversion\n");
    } else {
        printf("converted=%ld\n", v);
    }
}

int main(void) {
    check("");
    check("   "); /* 空白のみ */
    check("+");   /* 符号のみ */
    check("-");   /* 符号のみ */
    check("+ 1"); /* 符号の直後が数字でない */
    return 0;
}
実行結果
input="": no conversion
input="   ": no conversion
input="+": no conversion
input="-": no conversion
input="+ 1": no conversion

符号(+/-)と空白の扱い

strtolstrtodは先頭の空白を読み飛ばし、+または-の1文字の符号を受け付けます。

符号の直後には数字が必要で、そうでない場合は「数字なし」と判断されます。

複数の符号や途中の空白を許容しません。

範囲外とオーバーフロー

範囲外の検出はerrno==ERANGEで行い、整数ではLONG_MAX/LONG_MINへ張り付きます。

浮動小数ではHUGE_VAL0.0が返る可能性があります。

末尾に未変換文字が残るケースは別問題で、endptrの確認で対処します。

状況確認すべきこと代表的な戻り値
数字がないendptr == nptr値は未定義に頼らずエラー扱い
途中までしか読めない*endptr != '\0'のまま部分変換の値だがエラー扱いが安全
整数のオーバーフローerrno == ERANGELONG_MAXまたはLONG_MIN
浮動小数のオーバーフローerrno == ERANGEHUGE_VALの符号付き
浮動小数のアンダーフローerrno == ERANGE非常に小さい数または0.0

ポイントは「範囲エラー」と「未変換の残り」を別々にチェックすることです。

使い方の基本コード

整数の変換

厳密に整数へ変換するユーティリティ関数を用意すると、呼び出し元が簡潔になります。

C言語
/* 文字列を厳密にlongへ変換: 末尾のゴミを許さず、範囲エラーも検出 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>

bool parse_long_strict(const char *s, int base, long *out) {
    if (s == NULL || out == NULL) return false;

    errno = 0;
    char *end = NULL;
    long v = strtol(s, &end, base);

    /* 少なくとも1文字は変換できたか */
    if (end == s) return false;

    /* 範囲エラーか */
    if (errno == ERANGE) return false;

    /* 末尾は空白のみ許容する例 */
    while (*end != '
/* 文字列を厳密にlongへ変換: 末尾のゴミを許さず、範囲エラーも検出 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
bool parse_long_strict(const char *s, int base, long *out) {
if (s == NULL || out == NULL) return false;
errno = 0;
char *end = NULL;
long v = strtol(s, &end, base);
/* 少なくとも1文字は変換できたか */
if (end == s) return false;
/* 範囲エラーか */
if (errno == ERANGE) return false;
/* 末尾は空白のみ許容する例 */
while (*end != '\0' && isspace((unsigned char)*end)) end++;
if (*end != '\0') return false;
*out = v;
return true;
}
int main(void) {
const char *tests[] = { "42", "  -17", "123abc", "99999999999999999999", "0x10", NULL };
for (int i = 0; tests[i]; ++i) {
long v = 0;
bool ok = parse_long_strict(tests[i], 10, &v); /* ここでは10進固定 */
printf("input=%-24s -> %s", tests[i], ok ? "OK" : "NG");
if (ok) printf(", value=%ld", v);
printf("\n");
}
return 0;
}
' && isspace((unsigned char)*end)) end++; if (*end != '
/* 文字列を厳密にlongへ変換: 末尾のゴミを許さず、範囲エラーも検出 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
bool parse_long_strict(const char *s, int base, long *out) {
if (s == NULL || out == NULL) return false;
errno = 0;
char *end = NULL;
long v = strtol(s, &end, base);
/* 少なくとも1文字は変換できたか */
if (end == s) return false;
/* 範囲エラーか */
if (errno == ERANGE) return false;
/* 末尾は空白のみ許容する例 */
while (*end != '\0' && isspace((unsigned char)*end)) end++;
if (*end != '\0') return false;
*out = v;
return true;
}
int main(void) {
const char *tests[] = { "42", "  -17", "123abc", "99999999999999999999", "0x10", NULL };
for (int i = 0; tests[i]; ++i) {
long v = 0;
bool ok = parse_long_strict(tests[i], 10, &v); /* ここでは10進固定 */
printf("input=%-24s -> %s", tests[i], ok ? "OK" : "NG");
if (ok) printf(", value=%ld", v);
printf("\n");
}
return 0;
}
') return false; *out = v; return true; } int main(void) { const char *tests[] = { "42", " -17", "123abc", "99999999999999999999", "0x10", NULL }; for (int i = 0; tests[i]; ++i) { long v = 0; bool ok = parse_long_strict(tests[i], 10, &v); /* ここでは10進固定 */ printf("input=%-24s -> %s", tests[i], ok ? "OK" : "NG"); if (ok) printf(", value=%ld", v); printf("\n"); } return 0; }
実行結果
input=42                      -> OK, value=42
input=  -17                   -> OK, value=-17
input=123abc                  -> NG
input=99999999999999999999    -> NG
input=0x10                    -> NG

この例は10進のみ許容する方針です。

16進や8進も受け付けたい場合はbase=0を使うか、次節のように明示の基数を指定します。

基数(base)の指定

baseを使うと、2進〜36進まで対応できます。

base=0では自動判別になりますが、先頭0による8進解釈を避けたい場合は10を指定しましょう。

C言語
/* 基数指定の例: base=0の自動判別と明示的な基数 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

static void print_as(const char *s, int base) {
    errno = 0;
    char *end = NULL;
    long v = strtol(s, &end, base);
    printf("str=\"%s\", base=%d -> value=%ld, endptr_advanced=%s, errno=%d\n",
           s, base, v, end != s ? "yes" : "no", errno);
}

int main(void) {
    print_as("0x1f", 0);   /* 16進として31 */
    print_as("077", 0);    /* 8進として63 */
    print_as("1011", 2);   /* 2進として11 */
    print_as("1f", 16);    /* 16進として31 */
    print_as("077", 10);   /* 10進固定なら77として解釈 */
    return 0;
}
実行結果
str="0x1f", base=0 -> value=31, endptr_advanced=yes, errno=0
str="077", base=0 -> value=63, endptr_advanced=yes, errno=0
str="1011", base=2 -> value=11, endptr_advanced=yes, errno=0
str="1f", base=16 -> value=31, endptr_advanced=yes, errno=0
str="077", base=10 -> value=77, endptr_advanced=yes, errno=0

先頭0で8進になる罠があるため、ユーザー入力を想定するなら10進固定が安全です。

小数の変換

浮動小数はstrtodを用います。

INFやNANを許可するかは要件に応じて決めてください

C言語
/* 文字列を厳密にdoubleへ変換: INF/NANの許容を切り替え可能 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>

bool parse_double_strict(const char *s, bool allow_inf_nan, double *out) {
    if (s == NULL || out == NULL) return false;

    errno = 0;
    char *end = NULL;
    double v = strtod(s, &end);

    if (end == s) return false;        /* 数字なし */
    if (errno == ERANGE) return false; /* 範囲エラー */

    /* 末尾空白のみ許容 */
    while (*end != '
/* 文字列を厳密にdoubleへ変換: INF/NANの許容を切り替え可能 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>
bool parse_double_strict(const char *s, bool allow_inf_nan, double *out) {
if (s == NULL || out == NULL) return false;
errno = 0;
char *end = NULL;
double v = strtod(s, &end);
if (end == s) return false;        /* 数字なし */
if (errno == ERANGE) return false; /* 範囲エラー */
/* 末尾空白のみ許容 */
while (*end != '\0' && isspace((unsigned char)*end)) end++;
if (*end != '\0') return false;
/* INF/NANの可否 */
if (!allow_inf_nan) {
if (!isfinite(v)) return false; /* inf, -inf, nan を拒否 */
}
*out = v;
return true;
}
int main(void) {
const char *tests[] = { "3.14159", "1.2e-3", "nan", "INF", "1,23", "  -0.0  ", NULL };
for (int i = 0; tests[i]; ++i) {
double d = 0.0;
bool ok = parse_double_strict(tests[i], false, &d); /* INF/NANを拒否する設定 */
printf("input=%-10s -> %s", tests[i], ok ? "OK" : "NG");
if (ok) printf(", value=%g", d);
printf("\n");
}
return 0;
}
' && isspace((unsigned char)*end)) end++; if (*end != '
/* 文字列を厳密にdoubleへ変換: INF/NANの許容を切り替え可能 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>
bool parse_double_strict(const char *s, bool allow_inf_nan, double *out) {
if (s == NULL || out == NULL) return false;
errno = 0;
char *end = NULL;
double v = strtod(s, &end);
if (end == s) return false;        /* 数字なし */
if (errno == ERANGE) return false; /* 範囲エラー */
/* 末尾空白のみ許容 */
while (*end != '\0' && isspace((unsigned char)*end)) end++;
if (*end != '\0') return false;
/* INF/NANの可否 */
if (!allow_inf_nan) {
if (!isfinite(v)) return false; /* inf, -inf, nan を拒否 */
}
*out = v;
return true;
}
int main(void) {
const char *tests[] = { "3.14159", "1.2e-3", "nan", "INF", "1,23", "  -0.0  ", NULL };
for (int i = 0; tests[i]; ++i) {
double d = 0.0;
bool ok = parse_double_strict(tests[i], false, &d); /* INF/NANを拒否する設定 */
printf("input=%-10s -> %s", tests[i], ok ? "OK" : "NG");
if (ok) printf(", value=%g", d);
printf("\n");
}
return 0;
}
') return false; /* INF/NANの可否 */ if (!allow_inf_nan) { if (!isfinite(v)) return false; /* inf, -inf, nan を拒否 */ } *out = v; return true; } int main(void) { const char *tests[] = { "3.14159", "1.2e-3", "nan", "INF", "1,23", " -0.0 ", NULL }; for (int i = 0; tests[i]; ++i) { double d = 0.0; bool ok = parse_double_strict(tests[i], false, &d); /* INF/NANを拒否する設定 */ printf("input=%-10s -> %s", tests[i], ok ? "OK" : "NG"); if (ok) printf(", value=%g", d); printf("\n"); } return 0; }
実行結果
input=3.14159   -> OK, value=3.14159
input=1.2e-3    -> OK, value=0.0012
input=nan       -> NG
input=INF       -> NG
input=1,23      -> NG
input=  -0.0    -> OK, value=-0
注意

小数点の記号はロケールに依存します。

通常のCロケールでは.ですが、地域によっては,となることがあります。

ロケール依存を避けるにはsetlocale"C"に固定するか、プラットフォームに応じてstrtod_lなどを検討します。

エラー時の戻り値と処理

「失敗なら既定値を使う」というポリシーを関数に閉じ込めると呼び出し側が簡潔になります。

ただしログやメッセージで失敗を必ず記録しましょう。

C言語
/* 失敗時にデフォルト値を返し、理由をstderrへ出す */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>

long str_to_long_or_default(const char *s, int base, long def) {
    if (s == NULL) {
        fprintf(stderr, "[parse] NULL input, use default=%ld\n", def);
        return def;
    }
    errno = 0;
    char *end = NULL;
    long v = strtol(s, &end, base);

    if (end == s) {
        fprintf(stderr, "[parse] no digits: \"%s\", use default=%ld\n", s, def);
        return def;
    }
    if (errno == ERANGE) {
        fprintf(stderr, "[parse] ERANGE: \"%s\", use default=%ld\n", s, def);
        return def;
    }
    while (*end && isspace((unsigned char)*end)) end++;
    if (*end != '
/* 失敗時にデフォルト値を返し、理由をstderrへ出す */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
long str_to_long_or_default(const char *s, int base, long def) {
if (s == NULL) {
fprintf(stderr, "[parse] NULL input, use default=%ld\n", def);
return def;
}
errno = 0;
char *end = NULL;
long v = strtol(s, &end, base);
if (end == s) {
fprintf(stderr, "[parse] no digits: \"%s\", use default=%ld\n", s, def);
return def;
}
if (errno == ERANGE) {
fprintf(stderr, "[parse] ERANGE: \"%s\", use default=%ld\n", s, def);
return def;
}
while (*end && isspace((unsigned char)*end)) end++;
if (*end != '\0') {
fprintf(stderr, "[parse] trailing chars: \"%s\", use default=%ld\n", s, def);
return def;
}
return v;
}
int main(void) {
long a = str_to_long_or_default("123", 10, -1);
long b = str_to_long_or_default("123abc", 10, -1);
printf("a=%ld, b=%ld\n", a, b);
return 0;
}
') { fprintf(stderr, "[parse] trailing chars: \"%s\", use default=%ld\n", s, def); return def; } return v; } int main(void) { long a = str_to_long_or_default("123", 10, -1); long b = str_to_long_or_default("123abc", 10, -1); printf("a=%ld, b=%ld\n", a, b); return 0; }
実行結果
[parse] trailing chars: "abc", use default=-1
a=123, b=-1

安全なデフォルト値は、後続の処理で不正状態に気づける値にしましょう。

例えばIDなら-1、サイズなら0など、意味的に区別できる値が有効です。

ユーザー入力の検証

ユーザー入力はfgetsで1行単位に読み取り、末尾の改行を取り除いた上で厳密変換するのが定石です。

以下は関数化した例です。

C言語
/* 1行の文字列を厳密にlongへ検証する例: fgetsで読んだ後に使う */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <string.h>

bool validate_int_line(const char *line, long *out) {
    if (line == NULL || out == NULL) return false;

    /* 改行の除去 */
    size_t len = strcspn(line, "\r\n");
    char buf[256];
    if (len >= sizeof(buf)) return false; /* 長すぎる行は拒否 */
    memcpy(buf, line, len);
    buf[len] = '
/* 1行の文字列を厳密にlongへ検証する例: fgetsで読んだ後に使う */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <string.h>
bool validate_int_line(const char *line, long *out) {
if (line == NULL || out == NULL) return false;
/* 改行の除去 */
size_t len = strcspn(line, "\r\n");
char buf[256];
if (len >= sizeof(buf)) return false; /* 長すぎる行は拒否 */
memcpy(buf, line, len);
buf[len] = '\0';
errno = 0;
char *end = NULL;
long v = strtol(buf, &end, 10);
if (end == buf) return false;
if (errno == ERANGE) return false;
while (*end && isspace((unsigned char)*end)) end++;
if (*end != '\0') return false;
*out = v;
return true;
}
/* 実運用では: 
char line[256];
if (fgets(line, sizeof(line), stdin) && validate_int_line(line, &value)) { ... } */
'; errno = 0; char *end = NULL; long v = strtol(buf, &end, 10); if (end == buf) return false; if (errno == ERANGE) return false; while (*end && isspace((unsigned char)*end)) end++; if (*end != '
/* 1行の文字列を厳密にlongへ検証する例: fgetsで読んだ後に使う */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <string.h>
bool validate_int_line(const char *line, long *out) {
if (line == NULL || out == NULL) return false;
/* 改行の除去 */
size_t len = strcspn(line, "\r\n");
char buf[256];
if (len >= sizeof(buf)) return false; /* 長すぎる行は拒否 */
memcpy(buf, line, len);
buf[len] = '\0';
errno = 0;
char *end = NULL;
long v = strtol(buf, &end, 10);
if (end == buf) return false;
if (errno == ERANGE) return false;
while (*end && isspace((unsigned char)*end)) end++;
if (*end != '\0') return false;
*out = v;
return true;
}
/* 実運用では: 
char line[256];
if (fgets(line, sizeof(line), stdin) && validate_int_line(line, &value)) { ... } */
') return false; *out = v; return true; } /* 実運用では: char line[256]; if (fgets(line, sizeof(line), stdin) && validate_int_line(line, &value)) { ... } */

この関数は「長すぎる行」「数字なし」「範囲外」「末尾のゴミ」を全て排除します。

不正な入力を上流で止めることで、後段のロジックが単純化します。

初心者が避けたい落とし穴とコツ

atoi/atofを使わない理由

atoi/atofはエラーを報告しません

たとえば"abc""0"atoiは0を返すため、失敗と正当な0の区別ができません。

C言語
#include <stdio.h>
#include <stdlib.h>
int main(void){
    printf("atoi(\"abc\")=%d, atoi(\"0\")=%d\n", atoi("abc"), atoi("0"));
    return 0;
}
実行結果
atoi("abc")=0, atoi("0")=0

結果だけで失敗を判定できない関数は入力検証に使うべきではありません

endptrをNULLにしない

endptrNULLで呼ぶと未変換の残りを検出できません。

常にendptrを受け取り、どこまで読めたかを調べる習慣をつけましょう。

LONG_MAX/LONG_MINに注意

longのサイズは環境依存です。

Linuxの多くでは64bit、Windowsでは32bitが一般的です。

64bit整数が必要ならstrtolllong longLLONG_MAX/LLONG_MINの利用を検討してください。

末尾のゴミを見逃さない

「123abc」のような入力を受け入れてしまうと、意図しない挙動に繋がります。

endptrで末尾まで完全に消費できたか、空白以外の文字が残っていないかを必ず確認しましょう。

安全なデフォルト値

失敗時に返す既定値は、後続で異常を確実に検知できるものを選びます。

IDやインデックスなら-1、サイズなら0などが一般的です。

ログ出力と併用することで原因追跡が容易になります。

まとめ

文字列→数値変換を安全に行うには、strtol/strtodの「endptr」と「errno」を正しく使い、末尾のゴミと範囲エラーを確実に検出することが肝心です。

初心者のうちはatoi/atofを使用しないと決めてしまうのが最も安全です。

以下のポイントを常に意識してください。

  • 呼び出し前にerrno = 0、呼び出し後にerrno == ERANGEを確認する。
  • endptrで「数字なし」かどうかと、末尾の未変換文字の有無を確認する。
  • 必要に応じてbaseを指定し、10進固定ならbase=10を使う。
  • 浮動小数ではINF/NANの扱いとロケール依存性に注意する。

最後に、厳密変換ラッパー関数を自前で用意しておくと、プログラム全体の安全性と可読性が飛躍的に高まります。

今日紹介したパターンをテンプレート化し、入力検証の基盤として活用してください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

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

URLをコピーしました!