web-dev-qa-db-ja.com

文字列を整数に変換するsscanfとatoiの違いは何ですか?

gcc 4.4.4 c89

文字列を整数値に変換する方が良い方法。

私はatoiとsscanfの2つの異なる方法を試しました。両方とも期待どおりに機能します。

char digits[3] = "34";
int device_num = 0;

if(sscanf(digits, "%d", &device_num) == EOF) {
    fprintf(stderr, "WARNING: Incorrect value for device\n");
    return FALSE;
}

またはatoiを使用して

device_num = atoi(digits);

エラーをチェックできるので、sscanfの方が良いと考えていました。ただし、atoiはチェックを行いません。

63
ant2009

次の3つの選択肢があります。

  1. atoi

これは、パフォーマンスが重要なコードで使用している場合、おそらく最速ですが、エラー報告は行いません。文字列が整数で始まらない場合、0を返します。文字列が整数の後にジャンクを含む場合、最初の部分を変換し、残りを無視します。数が大きすぎてintに収まらない場合、動作は指定されていません。

  1. sscanf

いくつかのエラー報告、および保存するタイプ(char/short/int/long/long long/size_t/ptrdiff_t/intmax_tの署名付き/署名なしバージョン)について、多くの柔軟性があります。

戻り値は成功した変換の数であるため、文字列が整数で始まっていない場合、"%d"のスキャンは0を返します。 "%d%n"を使用して、別の変数に読み込まれた整数の後の最初の文字のインデックスを格納し、それによって文字列全体が変換されたかどうか、または後でジャンクがあるかどうかを確認できます。ただし、atoiと同様に、整数オーバーフローの動作は指定されていません。

  1. strtolとファミリー

呼び出しを行う前にerrnoを0に設定した場合、堅牢なエラーレポート。戻り値はオーバーフロー時に指定され、errnoが設定されます。 2〜36の任意の基数を選択するか、先頭に0x0をそれぞれ16進数と8進数として自動解釈するための基数として0を指定できます。変換するタイプの選択肢は、long/long long/intmax_tの署名付き/署名なしバージョンです。

より小さな型が必要な場合は、常に一時的なlongまたはunsigned long変数に結果を保存し、自分でオーバーフローをチェックできます。

これらの関数はポインター引数へのポインターを取るため、変換された整数に続く最初の文字へのポインターも無料で取得できるため、必要に応じて文字列全体が整数であったか、文字列内の後続のデータを解析できます。


個人的には、mostの目的でstrtolファミリーをお勧めします。あなたが迅速かつ汚い何かをしているなら、atoiはあなたのニーズを満たすかもしれません。

余談ですが、先頭の空白、記号などが受け入れられない数字を解析する必要がある場合があります。この場合、独自のforループを簡単に実行できます。たとえば、

for (x=0; (unsigned)*s-'0'<10; s++) 
    x=10*x+(*s-'0');

または、使用することができます(堅牢性のため):

if (isdigit(*s))
    x=strtol(s, &s, 10);
else /* error */ 
106
R..

*scanf()関数ファミリは、変換された値の数を返します。したがって、sscanf()が1を返すことを確認する必要があります。 EOFは「入力エラー」に対して返されます。つまり、ssacnf()EOFを決して返しません。

sscanf()の場合、関数はフォーマット文字列を解析し、整数をデコードする必要があります。 atoi()にはそのオーバーヘッドはありません。両方とも、範囲外の値が未定義の動作を引き起こすという問題に苦しんでいます。

strtol()またはstrtoul()関数を使用する必要があります。これらの関数は、エラー検出とチェックをはるかに優れたものにします。また、文字列全体が消費されたかどうかも通知します。

intが必要な場合は、常にstrtol()を使用し、戻り値をチェックして、INT_MININT_MAXの間にあるかどうかを確認できます。

10
Alok Singhal

@Rへ。errno呼び出しでのエラー検出についてstrtolをチェックするだけでは不十分だと思います。

long strtol (const char *String, char **EndPointer, int Base)

また、EndPointerのエラーを確認する必要があります。

4
PickBoy

R ..とPickBoyの回答を組み合わせて簡潔にする

long strtol (const char *String, char **EndPointer, int Base)

// examples
strtol(s, NULL, 10);
strtol(s, &s, 10);
2
Steven Penny

無効な文字列の入力や範囲の問題について心配がない場合は、最も単純なatoi()を使用してください

それ以外の場合、エラー/範囲の検出が最適なメソッドはatoi()でもsscanf()でもありません。 この良い答え すべての準備はatoi()でのエラーチェックの欠如とsomesscanf()でのエラーチェックの欠如を詳述しています。

strtol()は、文字列をintに変換する上で最も厳しい関数です。しかし、それはほんの始まりにすぎません。適切な使用法を示すための詳細な例を以下に示します。したがって、 accepted one の後のこの回答の理由を示します。

// Over-simplified use
int strtoi(const char *nptr) {
  int i = (int) strtol(nptr, (char **)NULL, 10);
  return i; 
}

これはatoi()に似ており、strtol()のエラー検出機能を使用しません。

strtol()を完全に使用するには、考慮すべきさまざまな機能があります。

  1. 検出なしno conversion:例:"xyz"、または""または"--0"?これらの場合、endptrnptrと一致します。

    char *endptr;
    int i = (int)strtol(nptr, &endptr, 10);
    if (nptr == endptr) return FAIL_NO_CONVERT;
    
  2. 文字列全体を変換するか、先頭部分のみを変換する必要がありますか:"123xyz"は大丈夫ですか?

    char *endptr;
    int i = (int)strtol(nptr, &endptr, 10);
    if (*endptr != '\0') return FAIL_EXTRA_JUNK;
    
  3. 値が非常に大きいかどうかを検出し、結果は"999999999999999999999999999999"のようなlongとして表現できません。

    errno = 0;
    long L = strtol(nptr, &endptr, 10);
    if (errno == ERANGE) return FAIL_OVERFLOW;
    
  4. 値がintではなくlongの範囲外であったかどうかを検出します。 intlongの範囲が同じ場合、このテストは不要です。

    long L = strtol(nptr, &endptr, 10);
    if (L < INT_MIN || L > INT_MAX) return FAIL_INT_OVERFLOW;
    
  5. 一部の実装は、C標準を超えて、 変換が実行されなかった場合はEINVALにerrno または errnoなどの追加の理由でEINVALを設定します。 Baseパラメーターは無効です。 。これらのerrno値をテストする最適な時期は実装に依存します。

これをすべてまとめる:(ニーズに合わせて調整する)

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

int strtoi(const char *nptr, int *error_code) {
  char *endptr;
  errno = 0;
  long i = strtol(nptr, &endptr, 10);

  #if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
  if (errno == ERANGE || i > INT_MAX || i < INT_MIN) {
    errno = ERANGE;
    i = i > 0 : INT_MAX : INT_MIN;
    *error_code = FAIL_INT_OVERFLOW;
  }
  #else
  if (errno == ERANGE) {
    *error_code = FAIL_OVERFLOW;
  }
  #endif

  else if (endptr == nptr) {
    *error_code = FAIL_NO_CONVERT;
  } else if (*endptr != '\0') {
    *error_code = FAIL_EXTRA_JUNK;
  } else if (errno) {
    *error_code = FAIL_IMPLEMENTATION_REASON;
  }
  return (int) i;
}

注:上記のすべての関数では、先行スペース、オプションの先行sign文字を使用でき、locale変更の影響を受けます。より制限的な変換を行うには、追加のコードが必要です。


注:非OPタイトルの変更は、強調をゆがめています。この答えは、元のタイトル「文字列を整数sscanfまたはatoiに変換する」により適しています。

2
chux

ユーザーが34abcを入力し、atoiに渡すと34が返されます。入力した値を検証する場合は、入力した文字列にisdigitを繰り返し使用する必要があります。

0
Raghuram