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

プロフィール 

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

友達申請フォーム 

この人と友達になる

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

 

2008/05/12 11:12|

 

しばらく忙しくて放置していますが、また春頃になれば、それなりに注力できる可能性があります。もっとも、そのころになれば、JSPカーネルではなく、ASPカーネルに対応することを考えるような気もしますが...。

さて、このたび、このブログで公開しているソースコードのライセンスに関して質問を受けました。このブログは、あくまでも実装レポートで、私が実装中に考えたことや、判断に迷ったことなどを交えながら、記録を残して行けたらと思って始めたものです。現在公開されている内容も、ざっと動作は見ていますが、いろいろバグも指摘されていますし、私自身も過去の記事のバグを見つけています。

そんな感じで、まだ現状では非常に完成度は低いと考えてください。それでもよければ、あくまでも自己責任の下で、自由に利用していただいてかまいません。ただし、(合法・非合法にかかわらず)反社会的または反人道的な用途への利用だけはご遠慮ください。報告も著作権表示も対価も不要ですが、何らかの形でご一報くださると管理人は喜びます。

なお、ブログの本文に関しては、リンクを貼るか、引用元を記載していただければ、常識の範囲で引用していただいてかまいません。

何か不明の点がありましたら、コメントなどでご質問ください。
2006/11/26 00:02|未分類TB:0CM:0

 

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

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

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

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

Powered By FC2ブログ 

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

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