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

プロフィール 

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

友達申請フォーム 

この人と友達になる

ホーム 全記事一覧 次ページ >>

 

2008/07/06 21:10|

 

順番からすると今回はatof関数を取り上げる必要がありそうです。この関数も、atol関数などと同様、エラー処理ができないので推奨できないもののひとつです。今回の実装でも、規格との互換性のために申し訳程度の実装しか行いません。

static __inline__ double atof(const char *__s)
{
  return strtod(__s, NULL);
}

このように、atol関数と同様、<stdlib.h>ヘッダ内ではインライン関数として定義し、同じ内容のものを外部関数としても定義します。内部的にstrtod関数を呼び出しますので、効率面からもatof関数を使用するメリットは何もありません。

ところで、strtod関数もそうなのですが、atof関数はC99から機能が拡張されています。というのも、C99では16進浮動小数点定数というものが導入されたこともあり、atof関数やstrtod関数でも16進浮動小数点数を解釈できるようになっています。詳しくはstrtod関数の回に解説したいと思います。
2006/07/28 01:54|一般ユーティリティTB:0CM:0

 

strtol関数はlong型を返しましたが、strtoul関数はunsigned long型を返します。基本的な違いはそれだけです。細かな部分では、符号無し整数しか扱いませんので、'-'符号が現れるとそこで処理を終えますし、オーバーフローの判定はLONG_MAXではなくULONG_MAXで行います。

strtoull関数はunsigned long long型を返す関数ですが、strtoul関数とほとんど内容は同じですので、具体的な解説は割愛します。strtoul関数の実装のうち、unsigned longの部分をunsigned long longに、ULONG_MAXの部分をULLONG_MAXに読み替えれば、strtoull関数になると思います。

それでは、strtoul関数の実装です。

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

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

unsigned long strtoul(const char * __restrict__ s, char ** __restrict__ endptr, int radix)
{
  unsigned long result;

  if (_space_sign(s, (const char**)&s) != 0)
    --s;  // '-'の位置まで戻す

  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 > (ULONG_MAX - d) / radix)
    {
      errno = ERANGE;
      result = ULONG_MAX;
    }
    else
    {
      result = result * radix + d;
    }
  }

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

  return result;
}

細部を除けばstrtol関数と違いはありませんので、strtol関数と異なる部分だけ解説したいと思います。

まず、strtol関数では_space_sign関数の返却値をsignフラグに格納していましたが、strtoul関数では_space_sign関数が非0(=負)を返した場合は、ポインタを1つ戻し、'-'文字を指すようにしています。

次に、LONG_MAXを用いてオーバーロード判定を行っていた箇所は、ULONG_MAXを用いて判定しています。その際、signフラグによる補正もなくなっています。また、オーバーフロー発生時は常にULONG_MAXを返すようにしています。

最後に、signフラグがないため、結果はresultをそのまま返すようにしています。

2006/07/19 10:26|一般ユーティリティTB:0CM:2

 

久々の更新になりますが、今回は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

 

前回のatoi関数は独自の実装でしたが、今回取り上げるatol関数とatoll関数は、それぞれstrtol関数とstrtoll関数のラッパになります。なお、strtol関数とstrtoll関数の実装については次回に取り上げる予定です。

実装ですが、まずは<stdlib.h>ヘッダ内でインライン関数として定義し、同じ内容の外部定義も用意することになります。

static __inline__ long atol(const char *__s)
{
  return strtol(__s, NULL, 10);
}

static __inline__ long long atoll(const char *__s)
{
  return strtoll(__s, NULL, 10);
}

atoll関数は、返却値型がlong longであることからも分かるように、C99で追加された関数です。もちろん、内部で呼び出しているstrtoll関数も同様です。
2006/07/01 09:40|一般ユーティリティTB:0CM:0

 

atoi関数は、エラー処理ができないこともあり、なるべくstrtol関数に置き換えるべき非推奨の関数です。しかし、エラーがないことが分かっている場合には、エラー判定の処理がないことと、10進数固定であることにより、strtol関数より効率がよいのも確かです。特に、long型とint型が異なるサイズの場合、その違いが顕著に現れます。

atol関数との対象性もあるので、やや悩むところではありますが、今回の実装では、atoi関数に関してはstrtol関数を使用せずに独自の実装を行うことにしました。atol関数については、別の機会に取り上げますが、strtol関数を内部で使用することにします。

atoi関数の実装に入る前に、atoi関数のほかに、strtol関数やstrtod関数などで共通に使用する下請け関数を定義します。この関数は、空白類文字を読み飛ばし、符号文字を判定して、負であれば-1を、それ以外は0を返す仕様です。また、空白類文字と符号文字の直後にポインタを位置付けます。

#include <ctype.h>

int _space_sign(const char *s, const char **endptr)
{
  while (isspace((unsigned char)*s))
    ++s;
  int sign = 0;
  switch (*s)
  {
  case '-':
    sign = -1;
    // fall through
  case '+':
    ++s;
    break;
  }
  *endptr = s;
  return sign;
}

なお、リンク後のプログラムサイズを最小にするため、この_space_sign関数は、それだけで単独のソースファイルに記述するようにします。そうしないと、strtod関数を使いたいだけの場合でも、atoi関数がリンクされてしまい、非常に空間効率が悪くなります。

それでは、atoi関数本体の実装です。

#include <ctype.h>

int atoi(const char *s)
{
  int sign = _space_sign(s, &s);
  int result;
  for (result = 0; isdigit((unsigned char)*s); s++)
    result = result * 10 + *s - '0';
  if (sign != 0)
    result = -result;
  return result;
}

本来であれば、for文の中でオーバーフローの判定を行うべきですが、仮にエラーを検出しても適切にレポートする方法がないため、何もせずに放置しています。その引き換えとして、文字列から数値への変換を高速に行うことができるようになっています。

2006/06/29 22:32|一般ユーティリティTB:0CM:0

ホーム 全記事一覧 次ページ >>

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

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

Powered By FC2ブログ 

Powered By FC2ブログ
ブログやるならFC2ブログ

Copyright(C) 2006 TAKAGI Nobuhisa All rights reserved.
Powered by FC2ブログ. 無料ホームページ アフィリエイト レンタルサーバー FC2ブログ 一戸建て template designed by 遥かなるわらしべ長者への挑戦.