ホーム
全記事一覧
|
|
|
_Exit関数はC99で追加された関数で、exit関数と同様、プログラムを終了させるためのものですが、atexit関数で登録した終了時関数の呼び出しを行わないなどの違いがあります。kernel_exit関数を使ってしまうと、call_atexit関数が呼び出されてしまうため、今回は別に実装することにします。
#include <t_services.h> #include <t_config.h>
extern BOOL _kernel_iniflg
void _Exit(int status) { if (_kernel_iniflg) { loc_cpu(); _kernel_iniflg = FALSE; } cpu_terminate(); sys_exit(); }
_kernel_iniflgは、vsns_iniサービスコールのための大域変数で、vsns_iniとは論理が逆になります。今回は、_kernel_iniflgをFALSEに設定する必要があるため、直接操作しています。このように、実行環境に密接にかかわる処理は、OSに対して完全に外付けにするのは困難です。
|
2006/05/24 08:09|一般ユーティリティ|TB:0|CM:0|▲
|
|
|
exit関数は、文字通りプログラムを終了させる関数ですが、実行環境であるTOPPERS/JSPカーネルには、ほぼ同じ動作をするkernel_exit関数がありますので、これを使うことにします。
#include <t_searvices.h> #include <t_config.h>
void exit(int status) { if (!vsns_ini()) kernel_exit(); call_atexit(); cpu_terminate(); sys_exit(); }
ここで、vsns_ini関数を使って、カーネルが起動しているかどうかを判別しています。カーネルが起動していない状態で、kernel_exit関数を呼び出すと、いろいろと問題が生じる可能性が懸念されるからです。
call_atexit();以降は、kernel_exit関数の後半部分と同じです。カーネルが起動していない状態であれば、CPUロック状態ですので、t_lock_cpu関数の呼び出しなどは行っていません。
kernel_exit関数の後半部分では、syslog_terminate関数の呼び出しを行っているのですが、静的APIのATT_INIで登録した初期化ルーチンが呼び出される前にsyslog_terminate関数を呼び出すと、まともに動くかどうかわからないので、ここでは省略しています。
exit関数の引数には、プログラムの終了コードを渡す必要がありますが、今回の実装ではこの値は無視しています。ただし、<stdlib,h>ヘッダでは、終了コードのためのマクロを2つ定義しなければなりません。
#define EXIT_SUCCESS 0 #define EXIT_FAILUE 1
このうち、EXIT_SUCCESSは0でなければなりませんが、EXIT_FAILUREは非0であれば何でもかまいませんので、今回は1にしておきました。
|
2006/05/24 01:55|一般ユーティリティ|TB:0|CM:0|▲
|
|
|
abort関数はプログラムを異常終了させるためのものです。「プログラムの終了」という処理の具体的な動作が実行環境に依存するため、必然的にabort関数の動作も実行環境に強く依存します。
今回は余り深く考えず、実行環境であるTOPPERS/JSPカーネルのターゲット依存部で定義されるkernel_abort関数を使用することにしました。
#include <signal.h>
void abort(void) { raise(SIGABRT); kernel_abort(); }
ご覧の通り、abort関数では、プログラムを終了させる前にSIGABRTシグナルを発生させます。したがって、SIGABRTのシグナル処理ルーチンから、longjmpなどの方法で脱出するか、ext_tskでタスクを終了させてしまえば、プログラムは終了しなくなります。
|
2006/05/24 01:10|一般ユーティリティ|TB:0|CM:0|▲
|
|
|
勢いに乗ってきたので、どんどん書きます。
div関数は、整数どうしの除算における商と剰余を同時に求めるための関数で、int型用がdiv、long型用がldiv、long long型用がlldivという名前になります。
C99より前は、一方のオペランドが負で、他方が正の除算の結果は処理系定義でしたので、div関数の実装では、一旦引数の絶対値を求めてから商と剰余を求め、後で調整する方式をとっていましたが、C99では、その必要もなくなりました。単に機能を満たすだけであれば、次の実装で十分です。
typedef struct { int quot; int rem; } div_t;
div_t div(int numer, int denom) { div_t result; result.quot = numer / denom; result.rem = numer % denom; return result; }
このように書くと、返却値のコピーが懸念されるのですが、実際のコンパイル結果を見る限りでは問題ありませんでした。
ところで、H8/300HやH8Sで、int型が16ビットの場合には、上記の実装で理想的なコードが出力されました。というのは、H8の除算命令を使って、商と剰余を同時に求めるように展開されていたのです。
ところが、int型が32ビットであったり、H8/300の場合は、そううまくはいきませんでした。H8/300は用途が少ないので無視するとしても、実行環境であるTOPPERS/JSPカーネルでは、int型を32ビットにしているので、結構大きな問題になります。
剰余の計算を、numer - result.quot * denomとすれば多少はましですが、できれば商と剰余を一発で求めたいものです。これについては、もう少し検討を続けたいと思います。
ldivおよびlldivに関しても、div関数と基本的には同じですが、16ビット以下になることはないので、はじめから剰余の求め方を変えておきます。
typedef struct { long quot; long rem; } ldiv_t;
ldiv_t ldiv(long numer, long denom) { ldiv_t result; result.quot = numer / denom; result.rem = numer - result.quot * denom; return result; }
typedef struct { long long quot; long long rem; } lldiv_t;
lldiv_t lldiv(long long numer, long long denom) { lldiv_t result; result.quot = numer / denom; result.rem = numer - result.quot * denom; return result; }
▽続きを読む▽
C++のdiv関数は、int型用だけではなく、long型用のものも多重定義されています。標準C++ではlong long型はサポートされませんが、GCCではサポートされるので、ついでにlong long型用のものも多重定義しておきましょう。
#ifdef __cplusplus inline ldiv_t div(long __numer, long __denom) { return ldiv(__numinline lldiv_t div(long long __numer, long long __denom) { return lldiv(__numer, __denom); } #endif
|
2006/05/24 00:52|一般ユーティリティ|TB:0|CM:0|▲
|
|
|
abs関数は、整数の絶対値を求める関数で、int型用がabs、long型用がlabs、long long型用がllabsという名前になります。浮動小数点数用のものは<math.h>ヘッダで、複素数用のものは<complex.h>ヘッダでそれぞれ宣言されますので、ここで扱うのは整数用のものだけです。
実装はいたって簡単で、次のようになります。
int abs(int j) { return j < 0 ? -j : j; }
labsおよびllabs関数も同様に実装することができますので、ここでは割愛します。この関数は非常に単純ですので、<stdlib.h>ヘッダ内でインライン関数として定義しておき、ユーザーが自分で関数原型を書いて呼び出したときのためだけに、外部定義も用意しておくことにします。
念のため、インライン関数の定義も挙げておきます。
static inline int abs(int __j) { return __j < 0 ? -__j : __j; }
jを予約済み識別子である__jに変更する以外は、特にこれといった違いはありません。
ところで、このabs関数ですが、引数がINT_MINの場合には、結果もINT_MINになってしまいます。標準規格では、結果が表現できない場合の動作は未定義なので、これでも問題ないのですが、ときどき罠にはまるので注意が必要です。
参考までに、今回は条件演算子を使ってあっさり実装しましたが、RISCのように分岐のコストが大きいプロセッサでは、次のように実装することがあります。
int t = j >> (sizeof(j)*CHAR_BIT-1); return (j ^ t) - t;
確かにこれで分岐はなくなります。H8の場合には、シフト演算のコストが分岐以上に大きいので、こうした実装は最適とはいえません。
▽続きを読む▽
C++のabs関数は、int型だけでなく、long型、float型、double型、long double型それぞれについて多重定義されています。long long型は標準C++ではサポートされないのですが、GCCではサポートされるので、long long型についても多重定義しておいた方がよいでしょう。
#ifdef __cplusplus inline long abs(long __j) { return __j < 0 ? -__j : __j; } inline long long abs(long long __j) { return __j < 0 ? -__j : __j; } inline float abs(float __x) { return __x < 0 ? -__x : __x; } inline double abs(double __x) { return __x < 0 ? -__x : __x; } inline long double abs(long double __x) { return __x < 0 ? -__x : __x; } #endif
他の型のものを勝手に定義してしまうわけにはいかないので、テンプレートは使わず、多重定義によって実装する必要があります。
|
2006/05/24 00:19|一般ユーティリティ|TB:0|CM:0|▲
|
|
|
これまでは、どちらかというと他のライブラリへの依存性が少ないシンプルなものを扱ってきました。今回からは、今までに実装した関数等を使わなければ実現できない機能を多く含む<stdlib.h>ヘッダの実装に入ります。
まずはヘッダの記述からです。<stdlib.h>ヘッダでは、いくつかの型やマクロの定義を行う必要があります。そのうち、size_t、wchar_t、およびNULLについては、<stddef.h>ヘッダと同じですので割愛します。また、div_t、ldiv_t、およびlldiv_t型についてはdiv関数の回に、EXIT_SUCCESSおよびEXIT_FAILUREマクロについてはexit関数の回に、RAND_MAXマクロについてはrand関数の回に、それぞれ取り上げます。
というわけで、最後に残ったのはMB_CUR_MAXマクロです。これは、現在のロケールにおける多バイト文字の最大バイト数です。今のところは"C"ロケールしか扱っていませんので、1に定義しておけばよいのですが、後々のことを考えて、ここでは外部変数を参照する方式を採用します。
#ifndef __cplusplus extern #else extern "C" #endif signed char _mb_cur_max; #define MB_CUR_MAX ((int)_mb_cur_max)
C++の場合にはextern "C"を付けるようにしています。GCCの場合、オブジェクトに関してはC結合もC++結合も違いはないのですが、念のための保険としてこのようにしておきます。
MB_CUR_MAXマクロの定義で、int型にキャストしているのは、こうしておかないとMB_CUR_MAXに代入したり、インクリメントしたりといった誤操作ができてしまうからです。規格の要件にはありませんが、MB_CUR_MAXは右辺値であるべきです。
|
2006/05/23 23:42|一般ユーティリティ|TB:0|CM:1|▲
|
|
|
raise関数は、明示的にシグナルを発生させるための関数です。非同期シグナルに対する処理とは異なり、raise関数と、raise関数を内部的に呼び出すabort関数によって呼び出されたシグナル処理ルーチンは、そこで行うことができる処理にほとんど制約がありません。
非同期シグナルの場合、シグナル処理ルーチンの中で呼び出せる標準関数は、abort関数と_Exit関数、そして同じシグナルに対するsignal関数だけです。今回の実装では、非同期シグナルは、タスク例外処理ルーチンからraise関数を呼び出すことで実現しますが、非同期の場合はraise関数を使っていても上記の制約があるものとします。
つまり、タスク例外処理ルーチンを介して呼び出されたシグナル処理ルーチンからは、ほとんどの標準関数を呼び出すことができず、longjmp関数を用いて脱出することもできなくなります。
ところで、前回のsignal関数の回には、raise関数をsignal関数と同じ翻訳単位に入れることを想定していましたが、よく考えてみるとその必要はなさそうです。では、実装をご覧ください。
#include <kernel.h> #include <signal.h>
int raise(int sig) { void (*func)(int) = signal(sig, SIG_DFL); uintptr_t f = (uintptr_t)func; if (f <= 2) { switch (f) { case 0: // SIG_DFL ext_tsk(); // break; case 1: // SIG_IGN break; default: return -1; } } else { (*func)(sig); } return 0; }
今回の実装では、SIG_DFLの処理は、問答無用でタスク終了としています。最初からswitch文で処理を分けてもよかったのですが、ユーザー定義のシグナル処理ルーチンを最速の方法で呼び出すには、いったんユーザー定義のものが登録されているのか、SIG_DFLやSIG_IGNが登録されているのかを「ふるい」にかけた方が得策と考えて、このように実装してみました。
|
2006/05/22 03:15|シグナル処理|TB:0|CM:0|▲
|
|
|
前回は、シグナル処理ルーチンの方針について書きました。今回と次回は、その方針に基づいて、実際にsignal関数とraise関数を実装することにします。
今回の関数は、初めてOSに依存したコードになります。といっても、簡単な排他制御だけですので、普通に割り込みを禁止してもよいのですが、他のターゲットへの移植性を考慮して、μITRONの機能を使うことにします。
それでは、実装を見てみましょう。
#include <signal.h> #include <errno.h>
#define SIGMAX 6 static void (*signal_handler_table[SIGMAX])(int);
void (* signal(int sig, void (*func)(int)) )(int) { if (sig < 1 || SIGMAX < sig || func == SIG_ERR) { errno = EDOM; return SIG_ERR; }
BOOL f = sns_loc(); loc_cpu(); void (*result)(int) = signal_handler_table[sig-1]; signal_handler_table[sig-1] = func; if (!f) unl_cpu();
return result; }
前半部は引数のチェックを行っています。sigで指定したシグナル番号の範囲と、念のためfuncにSIG_ERRを誤って指定していないかどうかだけのチェックにとどめています。funcに空ポインタを指定した場合、SIG_DFLとの区別が付きませんので、特に何もしていません。
エラーを検出した場合、SIG_ERRを返すとともに、errnoに正の値を設定しなければならないため、ここではEDOMを設定しています。他のエラー番号を設けてもよいのですが、今回はそこまでする必要もないと判断しました。
シグナル処理ルーチンは、signal_handler_table配列に格納されます。静的な配列ですので、その内容は空ポインタ(=SIG_DFL)で初期化されることが期待できます。また、raise関数も同じ翻訳単位に収めることにすれば、この配列は内部結合となり、外部から参照されるおそれがなくなります。
signal_handler_table配列の操作は、排他制御が必要ですので、sns_loc、loc_cpu、およびunl_cpuというサービスコールを使用しています。これらはCPUロック状態の制御を行うもので、すごく乱暴に言ってしまえば、割り込み禁止の制御を行っています。
signal関数は、非タスクコンテキストで呼ばれることを想定していません。そのため、sns_ctxでコンテキストの状態を調べてることなく、(iloc_cpuやiunl_cpuではなく)loc_cpuやunl_cpuを問答無用で呼び出しています。非タスクコンテキストに対応するするには、SIL*1を使った方が便利かと思います。
*1 ITRONデバイスドライバ設計ガイドラインの一部分として検討されているシステムインタフェースレイヤのこと。TOPPERS/JSPカーネルは、そのサブセットをサポートしています。
|
2006/05/16 00:07|シグナル処理|TB:0|CM:0|▲
|
|
|
個々の関数の実装に入る前に、シグナル処理ルーチンをどう扱うべきかについて、決めておきたいと思います。
そしてsignal関数の第2引数が関数を指している(すなわち、SIG_DFLやSIG_IGNではない)場合、シグナル sig が発生すると、シグナル処理ルーチンが呼び出される前に、次に挙げる動作のいずれかが行われます(処理系定義)。
- signal(sig, SIG_DFL);と同等のことを実行する。
- 少なくとも sig を含むシグナルの処理系定義の組に対して、処理系がそれらの発生を遮る(この状態は、その時点のシグナル処理ルーチンが完了された時点で元に戻される)。
多くの処理系では1.が採択されています。というより、C99より前の規格では、1.しかなかったように記憶しています。1.では、通常シグナル処理ルーチンの中で再度signal関数が呼び出されることになりますが、それより前に同じシグナルが発生してしまうことを防げないため、いろいろと問題があります。
可能であれば2.の方が優れている気もしますが、2.を実現するにはやや問題があります。というのは、raiseまたはabort関数の結果としてシグナル処理ルーチンが呼び出された場合、longjmp関数で脱出できる必要があるわけですが、シグナルを遮ってしまうと、それを元に戻すことができなくなるからです。
2.の方法でlongjmp関数を使った場合の問題点を回避するには、longjmp関数自体を修正する必要がありますが、下手なことをやるといろいろなところへの悪影響が懸念されるので、今回は素直に1.の方法を採用することにします。
次に、シグナル処理ルーチンが呼び出されるコンテキストをどうするかですが、今回は、すべてタスクコンテキストで呼び出されるものとします。割り込みハンドラやCPU例外ハンドラ(H8はCPU例外をサポートしませんが)などの非タスクコンテキストから直接呼ぶのではなく、いったんiras_texでタスク例外を発生させてから、シグナル例外処理ルーチンを呼び出すものとします。
このように、タスク例外処理ルーチンからシグナル処理ルーチンが呼ばれることになるわけですが、raiseやabort関数の結果としてシグナル処理ルーチンを呼び出す場合は、タスク例外を介さずに、直接シグナル処理ルーチンを呼び出すことにします。
というのは、タスク例外処理ルーチンがすべてのタスクに対して定義されているとは限らず、また、特定のタスクの例外処理ルーチンだけを呼び出すのも問題があるからです。しかも、タスク例外処理ルーチンが定義されているかどうかを調べることは(タスク初期化ブロックを直接覗かない限り)できないので、エラー検出ができないことも理由の一つです。
最後に残った課題は、タスク例外禁止状態の操作です。raiseやabort関数の結果としてシグナル処理ルーチンが呼ばれる場合には、タスク例外禁止状態を操作する必要は特にありません。タスク例外処理ルーチンから呼び出す場合が問題ですが、signal(sig, SIG_DFL);さえ実行しておけば、タスク例外禁止状態のままにしておいて、重複して発生したタスク例外を保留しても、規格に反するわけではなさそうなので、深く考えずに放置することにします。
タスク例外禁止状態で、タスク例外処理ルーチンからシグナル処理ルーチンを呼び出した場合、longjmp関数で脱出できなくなりますが、raiseやabort関数の結果としてシグナル処理ルーチンが呼ばれたのでない限り、abort、_Exit、およびsignal関数以外のライブラリ関数を呼び出した場合の動作は未定義なので、特に問題はなさそうです*1。
今回は、思いつくままダラダラと書いてみました。ちょっとわかりにくかったかもしれませんが、次回以降で各関数の実装を行っていきますので、その際に適宜参照していただければと思います。
*1 μITRON 4.0仕様では、タスク例外処理ルーチンからlongjmp関数で脱出することを許していますが、シグナル処理ルーチンが呼び出された以降は、μITRON 4.0仕様ではなく、標準Cライブラリの仕様に従うことになります。
|
2006/05/09 23:11|シグナル処理|TB:0|CM:0|▲
|
|
|
前回までで、ようやく<string.h>ヘッダを終えることができました。今回からは<signal.h>ヘッダの実装に入ります。
<signal.h>ヘッダで扱うシグナル処理は、最低限の実装だけで済ませると、ほとんど使い物にならず、規格との互換性のためだけに存在することになってしまいます。かといって、POSIX互換のような実装を行うのは、今回の目的を大きく超えています。
そこで、μITRON 4.0仕様に元々備わっているタスク例外の機能と組み合わせることで、(他の実行環境との)移植性を保ちながら、アプリケーションを作成できる程度のものにとどめることにします。すなわち、タスク例外を直接扱うと、μITRON以外の実行環境への移植性が損なわれますので、それをうまく回避できるようなものにしていきます。
とりあえずは、型とマクロの定義から行います。
まずは、sig_atomic_t型の定義です。この型は、アトミックオペレーションとして読み書きを行うことができる整数型ですが、以前、<stdint.h>ヘッダで定義されるSIG_ATOMIC_MAXおよびSIG_ATOMIC_MINの実装で、H8/300のときはshort型、それ以外はint型にすると決めました。
そこで、次のように型定義を行うことにします。
#ifdef __H8300__ typedef short sig_atomic_t; #else typedef int sig_atomic_t; #endif
次に、signal関数の第2引数または返却値となるSIG_~マクロの定義です。これは、他の関数へのポインタと同じ値にならなければよいので、次のように定義します。
#define SIG_DFL ((void(*)(int))0) #define SIG_IGN ((void(*)(int))1) #define SIG_ERR ((void(*)(int))2)
SIG_ERRは-1に定義される場合が多いのですが、ユーザ定義のシグナル処理ルーチンかどうかの判定を簡略化し、呼び出し効率を上げるためにあえてこのようにしています。また、H8の場合、0番地からベクタテーブルが割り付けられているため、これらの値が他の関数へのポインタと合致することもありません。
定義すべきマクロの最後は、signal関数およびraise関数の第1引数となるSIG~マクロです。SIG~マクロは、処理系が独自に追加してもよいのですが、ここでは最小限の内容にとどめます。これらのマクロの値は、1以上の整数でなければならないので、単純に連番をあてることにします。
#define SIGABRT 1 #define SIGFPE 2 #define SIGILL 3 #define SIGINT 4 #define SIGSEGV 5 #define SIGTERM 6
後は、signal関数とraise関数の宣言が必要です。関数の詳細については、例によって、個別にお話したいと思います。
|
2006/05/05 12:07|シグナル処理|TB:0|CM:0|▲
|
|
|
strncmp関数も、strncat関数と同様、素直な仕様かと思います。strcmp関数に文字数制限の判定だけを追加すれば実現できそうです。それでは、実装です。
#include <stddef.h>
int strncmp(const char *s1, const char *s2, size_t n) { register const unsigned char *ss1, *ss2, *t; for (ss1 = (const unsigned char*)s1, ss2 = (const unsigned char*)s2, t = ss1 + n; ss1 != t && *ss1 == *ss2 && *ss1 != '\0'; ss1++, ss2++) ; return *ss1 - *ss2; }
これも、strcmp関数と見比べていただければ、動作の違いがよくわかるかと思います。
2月から長々と取り組んできた<string.h>ヘッダの内容ですが、これで一通り終わり(のはず)です。次回からは、シグナル処理を扱う予定です。
|
2006/05/05 11:14|文字列操作|TB:0|CM:0|▲
|
|
|
今回はstrncat関数です。この関数は、strcat関数の文字数制限版といった位置付けですが、strcpy関数の文字数制限版であるstrcpy関数とは動作がかなり異なります。
strncpy関数では、コピー元の文字列が文字数に満たない場合は、残りの領域をナル文字で埋めていました。また、コピー元の文字列が指定した文字数より長い場合には、末尾のナル文字は付加されませんでした。
しかし、strncat関数は、残りの領域をナル文字で埋めることもなければ、末尾にナル文字が付加されないこともありません。どちらかといえば、より直感的でわかりやすい仕様だといえます。それでは実装です。
#include <stddef.h>
char *strncat(char * __restrict__ s1, const char * __restrict__ s2, size_t n) { register char *ss, *t; for (ss = s1; *ss != '\0'; ss++) ; for (t = ss + n; ss != t && (*ss = *s2) != '\0'; ss++, s2++) ; if (ss == t) *ss = '\0'; return s1; }
strcat関数およびstrncpy関数と比べていただければ、動作の違いを把握していただけるかと思います。
|
2006/05/05 11:00|文字列操作|TB:0|CM:0|▲
|
|
|
あまりにものらくらやってきたので、どの関数がまだだったのか、把握し切れているかどうか不安なのですが、<string.h>ヘッダに関しては、strncpy、strncat、strncmp関数で終わりだったかと思います。過不足に気づかれた方はご指摘ください。
というわけで、今回はstrncpy関数です。strncpy関数の仕様は、何となく変なのですが、末尾のナル文字が格納されることが保障されません。また、s2文字列がより短い場合、残りの部分にナル文字が書き込まれます。それでは、実装を見てみましょう。
#include <stddef.h>
char *strncpy(char * __restrict__ s1, const char * __restrict__ s2, size_t n) { register char *ss, *t; for (ss = s1, t = ss + n; ss != t && (*ss = *s2) != '\0'; ss++, s2++) ; for (; ss != t; ss++) *ss = '\0'; return s1; }
ご覧の通り、見たままの内容ですが、他の関数同様、nをデクリメントするのではなく、終端を指すポインタtを宣言して、文字数を管理するようにしています。
|
2006/05/04 00:20|文字列操作|TB:0|CM:2|▲
|
|
|
strspn関数は、strcspn関数とよく似ていますが、条件が逆になります。すなわち、指定した文字群の文字だけで構成される先頭部分の長さを求めます。言い換えれば、指定した文字群に含まれない文字が最初に現れた位置の添え字を返します。
機能は似ているのですが、実現方法はstrcspn関数とは結構違います。実装を見てみましょう。
#include <stddef.h>
size_t strspn(const char *s1, const char *s2) { register const char *ss1 = s1, *ss2; if (*s2 != '\0') { for (register int c; (c = *ss1) != '\0'; ss1++) { for (ss2 = s2; *ss2 != '\0'; ss2++) if (c == *ss2) break; if (*ss2 == '\0') break; } } return ss1 - s1; }
strcspn関数では、s1文字列の各文字が、s2文字列のどれかと一致した時点で即ループを抜ければよかったのですが、strspn関数では、s2文字列のどの文字とも一致しないことを確認しなければなりません。そのため、やや条件判定が複雑になっています。
|
2006/05/03 12:06|文字列操作|TB:0|CM:0|▲
|
|
|
<string.h>ヘッダの関数が続いていますが、この辺で少しピッチを上げて、次のヘッダに早く移りたいと思います。
今回は、strcspn関数ですが、前回のstrpbrk関数とやっていることは大差ありません。strpbrk関数は、文字列の中から指定した文字群に含まれる文字を探し、最初に見つかった位置を返しますが、strcspn関数は、指定した文字群以外から構成される先頭部分の長さを求めます。つまり、指定した文字郡に含まれる文字が最初に現れた位置の添え字を返すことになります。
それでは実装です。
#include <stddef.h>
size_t strcspn(const char *s1, const char *s2) { register const char *ss1 = s1; for (register int c; (c = *ss1) != '\0'; ss1++) for (register const char *ss2 = s2; *ss2 != '\0'; ss2++) if (c == *ss2) goto quit; quit: return ss1 - s1; }
s1の値を文字する必要があるため、いったんss1で受けてはいますが、それ以外はstrpbrk関数とほぼ同じです。後は、返却値が異なるので、return文の式が変わっています。ループからの脱出は、直接returnで抜けてもよいのですが、それは結局、コンパイル時の最適化でreturn ss1 - s1;のところに分岐することを期待しているので、だったら、最初からgotoで記述することにしました。
|
2006/05/03 11:35|文字列操作|TB:0|CM:0|▲
|
|
|
ホーム
全記事一覧
|