fc2ブログ

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

プロフィール 

高木信尚

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

ブロとも申請フォーム 

この人とブロともになる

ホーム 全記事一覧

 

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

 

しばらく更新をサボっていました。しかも、ほとんどセットものであるmbtowc、wctomb、mbstowcsと書いたのに、なぜかwcstombsだけ残っていました。というわけで、今回はwcstombs関数です。といっても、"C"ロケールにしか対応しなければ、やることはmbstowcs関数とほとんど同じです。

#include <stddef.h>

size_t wcstombs(char * __restrict__ s,
                const wchar_t * __restrict__ pwcs,
                size_t n)
{
  register char *p = s;
  for (char *t = p + n;
      p != t && (*p = (unsigned char)*pwcs) != '\0';
      p++, pwcs++)
    ;
  return p - s;
}

後になって気付いたのですが、ワイド文字の値がUCHAR_MAXより大きい場合はエラーにした方がよいのかもしれません。いや、多分そうすべきなのでしょう。これについては、<wchar.h>ヘッダの実装時に改めて検討しなおすことにしたいと思います。
2006/06/28 11:43|一般ユーティリティTB:0CM:0

 

よく似た名前の関数が続きますが、今回は、多バイト文字列からワイド文字列に変換するmbstowcs関数です。例によって、現時点では"C"ロケールにしか対応しませんので、実装は簡単です。

#include <stddef.h>

size_t mbstowcs(wchar_t * __restrict__ pwcs,
                const char * __restrict__ s,
                size_t n)
{
  register wchar_t *p = pwcs;
  for (wchar_t *t = p + n;
      p != t && (*p = (unsigned char)*s) != L'\0';
      p++, s++)
    ;
  return p - pwcs;
}

この関数は、書き込んだワイド文字数を返しますが、n文字を超えて書き込むことはなく、返却値がnの場合には、終端にナルワイド文字が格納されません。

"C"ロケール以外に対応させるには、内部的にmbtowc関数を呼び出した方がよいのかもしれませんが、全てのライブラリ関数はmbtowc関数を呼び出さないかのように振舞わなければなりませんので、実際にはもう一工夫する必要がありそうです。
2006/06/19 06:31|一般ユーティリティTB:0CM:0

 

wctomb関数は、mbtowc関数の逆で、ワイド文字から多バイト文字へかんかんするための関数です。例によって、現時点では"C"ロケールにしか対応しませんので、実装はいたって簡単です。

#include <stddef.h>

int wctomb(char *s, wchar_t wc)
{
  if (s == NULL)
    return 0;
  *s = (char)wc;
  return 1;
}

sが空ポインタの場合、多バイト文字がシフトシーケンスに依存する場合は非0を、それ以外は0を返す必要があるので、今回は0を返しています。また、wcがナルワイド文字の場合は、シフトシーケンスを初期状態に戻したりといった仕様がありますが、ここでは関係ないので反映されていません。

ところで、sが指す配列はMB_CUR_MAXバイト以上であることが要求されています。C99より前のバージョンの場合、可変長配列が使えませんので、汎整数式とは限らないMB_CUR_MAXを配列の要素数とすることはできず、代わりにMB_LEN_MAXを使わざるを得ませんでしたが、C99ではMB_CUR_MAXを使うことができます。ただし、実際のところ、どちらが効率がよいかは微妙なところです。

2006/06/15 10:56|一般ユーティリティTB:0CM:0

 

今回は多バイト文字からワイド文字に変換するためのmbtowc関数です。mbtowc関数はmblen関数とほとんど変わりません。実際、mblen関数は、

mbtowc(NULL, s, n)

と等価だからです。つまり、mbtowc関数とmblen関数の違いは、変換結果を格納するかどうかの違いでしかありません。それでは実装です。

#include <stddef.h>

int mbtowc(wchar_t *pwc, const char *s, size_t n)
{
  if (s == NULL || *s == '\0' || n == 0)
    return 0;
  if (pwc != NULL)
    *pwc = (unsigned char)*s;
  return 1;
}

現時点では、"C"ロケールにしか対応しない方針ですので、多バイト文字をそのままワイド文字に置き換えています。ここで、(unsigned char)でキャストしているのは、値を0~UCHAR_MAXにするためです。すなわち、0x80~0xffの範囲の値に対する対策です。
2006/06/14 15:37|一般ユーティリティTB:0CM:3

 

mblen関数は、多バイト文字を構成するバイト数を求めるための関数です。ただし、現在のところ、扱っているのが"C"ロケールだけですので、有効な多バイト文字のバイト数は常に1になります。

この関数の基本的な機能は単純なのですが、付随する機能がいろいろとあります。

まず、引数に空ポインタを渡した場合の動作ですが、多バイト文字がシフトシーケンスに依存する場合は非0を、そうでなければ0を返します。今回の実装では、シフトシーケンスに依存するエンコーディングは使用しませんので、この場合は0を返すことになります。

次に、先頭の1バイトが'\0'の場合にも0を返します。他には、サイズとして0を渡した場合にも0を返すことになります。それでは実装です。

#include <stddef.h>

int mblen(const char *s, size_t n)
{
  if (s == NULL || *s == '\0' || n == 0)
  &nbsp return 0;
  return 1;
}

*sの値をもう少し詳しく見て、有効な文字かどうかを判別してもよいのでしょうが、とりあえずナル文字以外は何でもOKという仕様にしておきます。
2006/06/13 00:16|一般ユーティリティTB:0CM:0

 

前回は擬似乱数の種を設定するsrand関数でしたので、今回は実際に擬似乱数を発生させるrand関数について書くことにします。

実際の関数の実装に先立って、擬似乱数として返す値の最大値を表す RAND_MAX マクロを定義する必要があります。このマクロの値は、ほとんどの処理系で32767ですので、今回の実装もそれにあわせることにします。

#define RAND_MAX 32767

次に、実際の関数の実装に入ります。rand関数は、srand関数のときに現れた静的変数 next を共有しますので、srand関数と同じ翻訳単位で定義する必要があります。あとは、擬似乱数の計算処理、より正確には next の更新処理の部分の排他制御が必要なのが特徴です。

int rand(void)
{
  int loc = vsns_ini() || sns_loc();
  if (!loc)
    loc_cpu();

  next = next * 1103515245 + 12345;
  int result = (unsigned int)(next / 65536) % (RAND_MAX+1);

  if (!loc)
    unl_cpu();
  return result;
}

擬似乱数の生成に使用する計算式は、標準規格の例と同じ単純なものです。擬似乱数の性能自体は対して高くありませんが、rand関数の用途の多くは、ちょっとした乱数が欲しいときに使うといったものでしょうから、これで十分です。

数値シミュレーションのようなものをH8で行うことがあるのかどうかは不明ですが、より高性能な擬似乱数が必要な場合は、アプリケーション側でM系乱数などのアルゴリズムを用いて、別途関数を作成することになると思います。
2006/06/12 13:32|一般ユーティリティTB:0CM:0

 

srand関数は、乱数の種を設定するための関数です。この関数で、適切に種を設定しないと、rand関数は常に同じ数列を返すことになります。

ところで、乱数の種としてsrand関数に渡す値には、time関数の返却値を使うことが多いようですが、今回の実装では、time関数はデフォルトでは (time_t)-1 を返すことになりそうですので、あらかじめお断りしておきます。

それでは実装です(2006/06/08 若干間違いがあったので修正しました)。

#include <kernel.h>

static unsigned long next = 1UL;

void srand(unsigned int seed)
{
#ifdef __H8300__
  int loc = vsns_ini() || sns_loc();
  if (!loc)
    loc_cpu();
#endif
  next = seed;
#ifdef __H8300__
  if (!loc)
    unl_cpu();
#endif
}

H8/300の場合、unsigned long型のオブジェクトへの代入はアトミックオペレーションにならないため、loc_cpuで排他制御を行っています。もっとも、現状のTOPPERS/JSPカーネルは、H8/300に対応していないので、このような措置は不要かもしれませんが、とりあえずこうしておきます。

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

 

多くの処理系では、system関数を使うと子プロセスを呼び出すことができるので、system関数は子プロセスを呼び出すための関数だと勘違いされている方も多いようです。実際には、system関数はコマンドプロセッサを呼び出すためのものです。

コマンドプロセッサというのは、普通はシェルと呼ばれることが多いと思います。Unixではshですし、Windowsではcommand.comやcmd.exeがこれに当たります。今回の実行環境では、コマンドプロセッサに相当するものは存在しません。

system関数の引数にNULLを渡すと、コマンドプロセッサを利用できるかどうかを調べることができます。非0が返ると使用可、0が返ると使用不可です。そして、引数にNULL以外を渡すと、処理系定義の値が返ってきます。多くの場合、ここで子プロセスの終了コードが返ってくるわけです。

今回の実装では、面倒なので、system関数は常に0を返すことにしてもよいのですが、誤使用に備えて、NULLを渡したときには0を、NULL以外を渡したときはEXIT_FAILUREを返すようにします。

#include <stdlib.h>

int system(const char *s)
{
  return s == NULL ? 0 : EXIT_FAILURE;
}

この関数も、規格の要求を満たすためだけのものですが、コマンドプロセッサを自作する場合には、実際にそれを呼び出すようにしてもよいでしょう。
2006/06/02 10:55|一般ユーティリティTB:0CM:0

 

getenv関数は、環境変数を取得するためのものですが、μITRONの実行環境には、環境変数などというものは存在しないため、常に空ポインタを返すことになります。

#include <stddef.h>

char *getenv(const char *s)
{
  return NULL;
}

この関数は、規格の要求を満たすためだけに用意しますが、必要に応じて、いくつかの環境変数を返せるようにするのもよいでしょう。
2006/06/02 00:24|一般ユーティリティTB:0CM:0

 

atexit関数は、終了時に呼び出される処理を登録するための関数です。この関数を実装するには、組み込みでは扱われることが少ない、プログラムの終了について、少し突っ込んで検討する必要があります。

TOPPERS/JSPカーネルでは、atexit関数で登録した処理を呼び出すために、call_atexit関数というのをターゲット依存部で定義することになっています。この関数が何を行うかというと、内部でsoftware_term_hookを呼び出しています。

atexit関数を使用する場合、software_term_hookは、atexit関数で登録した処理を後入れ先出しで実行するように記述してやる必要があります。

#define  ATEXIT_MAX  (32 + 1)

static void (*atexit_table[ATEXIT_MAX])(void);
static int atexit_num;

void software_term_hook(void)
{
  int i;

  for (i = atexit_num - 1; i >= 0; i--)
    (*atexit_table[i])();
}

実際には、software_term_hook関数では、上記の処理だけでなく、標準ストリームのクローズ処理を行う必要があるのですが、これについては、<stdio.h>ヘッダを実装する際に先送りすることにします。

ところで、atexit関数で登録可能な最大の数は、少なくとも32関数です。ここでは、32+1とすることで、1関数余分に登録できるようにしています。これは、C++を使ったときに、静的オブジェクトのデストラクタを呼び出すためのものです。

それでは、atexit関数の本体の実装を示します。

#include <kernel.h>

int atexit(void (*func)(void))
{
  int result = -1;
  _Bool sync = !vsns_ini();

  if (sync)
    loc_cpu();

  if (atexit_num < ATEXIT_MAX) {
    atexit_table[atexit_num++] = func;
    result = 0;
  }

  if (sync)
    unl_cpu();

  return result;
}

排他制御にはloc_cpuを使用していますが、サービスコールを呼び出すことができるのは、カーネルが動作しているときだけですので、カーネルの動作状態をvsns_iniであらかじめ調べています。カーネルが動いていないときは、排他制御の必要はありませんので、そのまま配列を操作しています。

2006/06/02 00:20|一般ユーティリティTB:0CM:0

ホーム 全記事一覧

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

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

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