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

プロフィール 

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

友達申請フォーム 

この人と友達になる

ホーム 全記事一覧

 

2008/07/06 21:08|

 

順番からすると今回は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

 

このブログにとっては初めてのテンプレート変更です。ソースコードを書くには横幅がある程度必要ですので、今回の変更に踏み切りました。これで多少は本文が見やすくなったかと思います。
2006/07/17 20:40|未分類TB:0CM:0

 

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

ホーム 全記事一覧

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

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

Powered By FC2ブログ 

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

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