fc2ブログ

C99に対応した標準Cライブラリの実装レポートを行っていきます。

プロフィール 

高木信尚

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

ブロとも申請フォーム 

この人とブロともになる

ホーム 全記事一覧 << 前の記事 次の記事 >>

 

久々の更新になりますが、今回はstrtol関数とstrtoll関数です。これらの関数は、返却値の型がlong型かlong long型かの違いだけですので、ここではstrtol関数に絞って書くことにします。strtoll関数はlong型のところをlong long型に、LONG_MAXのところをLLONG_MAXに読み替えればよいだけです。

それでは早速実装を見てみましょう。

#include <limits.h>
#include <ctype.h>
#include <errno.h>

int _space_sign(const char *s, const char **endptr);

long strtol(const char * __restrict__ s, char ** __restrict__ endptr, int radix)
{
  int sign = _space_sign(s, (const char**)&s);
  long result;

  if (s[0] == '0')
  {
    ++s;
    if ((s[1] | 0x20) == 'x')
    {
      if (radix == 0 || radix == 16)
      {
        ++s;
        radix = 16;
      }
    }
    else if (radix == 0)
      radix = 8;
  }
  else if (radix == 0)
    radix = 10;

  int c;
  for (result = 0; c = tolower((unsigned char)*s), isdigit(c) || ('a' <= c && c <= 'z'); s++)
  {
    int d = isdigit(c) ? c - '0' : c - 'a' + 10;
    if (d >= radix)
      break;
    if (result > (LONG_MAX - d - sign) / radix)
    {
      errno = ERANGE;
      result = sign ? LONG_MIN : LONG_MAX;
    }
    else
    {
      result = result * radix + d;
    }
  }

  if (endptr != NULL)
    *endptr = (char*)s;

  if (sign != 0)
    result = -result;
  return result;
}

このあたりの関数になってくると、ソースコードもそれなりに複雑になってきますが、ポイントだけをかいつまんで解説することにします。そろそろブログ上でもソースにまともなコメントを入れた方がよさそうな気がしてきましたが、とりあえずこのままで突き進みます。

まず、最初にatoi関数の回に作成した_space_sign関数を呼び出して、空白類文字の読み飛ばしと符号の判定を行っています。この際、仮引数 s の__restrict__修飾子が原因で警告が出るので、const char**で &s をキャストしています。あまり望ましくない回避方法かもしれませんが、実害はないのでこうしています。

次に、16進または8進の接頭辞である 0x および 0 の処理を行っています。0x の処理では、文字を強制的に小文字化するために、s[1] | 0x20という手法を採っています。この手法は移植性はありませんが、まともに'x'と'X'の両方を判定したり、tolower関数を使うより高速です。

その次に現れるfor文はatoi関数を拡張したようなものです。扱う数字が'0'から'9'だけでなく、'a'から'z'を10から35に解釈しなければならないこともあり、またオーバーフローの判定が必要なこともあって、かなり複雑になっています。

先ほどの小文字化の強制のときもそうであったように、ここでも英字の判定は処理系に依存します。標準Cでは、英字の連続性は保証されませんが、今回は対象とする処理系が固定ですので、敢えて規格厳密合致プログラムを捨てて効率を優先しています。

ちなみにisalnum関数を使用していないのは、将来"C"ロケール以外に対応したときのことを考慮してです。"C"ロケール以外では、isalnum関数がどんな文字に対して真を返すか分からないので、正確に'A'から'Z'および'a'から'z'だけを検出したい場合には使えないのです。

result > (LONG_MAX - d - sign) / radix という条件式はオーバーフローの判定ですが、かなり難解になってしまいました。というのも、値が負の場合の境界はLONG_MINであり、これは -LONG_MAX-1 であるため、絶対値が異なるからです。そこで負であることを表すフラグ sign を用いて補正しています。後は見ての通りの内容ですので、説明は不要かと思います。

なお、strtol関数やstrtoll関数は、"C"ロケール以外ではもっと別の形式を解釈できてもよいことになっています。例えば、3桁ごとの区切り文字をlconv構造体のthouosands_sepフィールドを元に解釈してもよいですし、極端な話、漢数字を解釈してもよいわけですが、多くの場合、そこまでする必要性もないですし、仮にそのような用途があっても、別の専用関数を用意した方がよいでしょう。

2006/07/19 バグがいくつか見つかったので修正しました。
2006/07/17 20:16|一般ユーティリティTB:0CM:2

コメント

> ++s;
> if ((s[1] | 0x20) == 'x')
一行前でsはインクリメントされているから、
if ((s[0] ...
では?

> long result;
最後に符号を調節する場合、中間結果は
-LONG_MINを保持できる必要があり、longでは、オーバーフローする。

> *endptr = (char*)s;
変換対象の数字列がなかった場合、先頭の空白スキップを行う前の s を *endptr に代入する必要があるが、その処理がなされていない。
(注: sが"0x"の後に非16進文字が続く文字列を指している場合、*endptrに代入する値は、'0'の後の'x'を指している必要があるが、ほとんどの処理系がバグっている。windows上の処理系は、試した範囲内では全滅。)

マイケル #-|2006/08/06(日) 16:58 [ 編集 ]


マイケルさん、コメントありがとうございます。
返事が遅くなってすみません。
すぐに対応できないのですが、時間を取ってもう一度見直してみます。
たかぎ #ftr86F3A|2006/08/11(金) 11:44 [ 編集 ]

コメントの投稿

管理者にだけ表示を許可する


トラックバック
トラックバックURLはこちら
http://libc.blog47.fc2.com/tb.php/76-f950cbf7

ホーム 全記事一覧 << 前の記事 次の記事 >>

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

このブログをリンクに追加する

Copyright(C) 2006 TAKAGI Nobuhisa All rights reserved.
Powered by FC2ブログ. template designed by 遥かなるわらしべ長者への挑戦.