● fixed X ユーティリティー
ワークエリア
fixed X ユーティリティー関数の多くは、ワークエリア(work area)を必要とします。呼び出し側は、各関数に必要なワークエリアを確保し、関数に与えなければなりません。
原理
一般的に、処理がある程度複雑な関数では、内部処理で必要な領域を自分自身で確保・使用します。
例えば、フィボナッチ数列を unsigned long で実装すると、
例1.
unsigned long fibonacci(unsigned long n)
{
unsigned long h,l,temp;
for(h = l = 1;n;n--) temp = h + l, l = h, h = temp;
return h;
}
下線のローカル変数が、関数内だけで使用される一時的なワークエリア、という事になります。C/C++で定められている変数型を使用する限り、上記のように簡単に作業用の領域を確保する事ができます。
これを、fixed X を使って、数値幅が不明の処理に素直に変更すると、
例2.
int fibonacci(unsigned long n,PFIXEDVAL presult,int len)
{
PFIXEDVAL ph,pl,ptemp;
if ((ptemp = calloc(3,len)) == 0) return 0; /* 処理に必要な領域を確保 */
ph = ptemp + len; pl = ph + len;
for(*ph = *pl = 1;n;n--){
memcpy(ptemp,ph,len);
fixed_add(ptemp,pl,len); /* temp = h + l */
memcpy(pl,ph,len); /* l = h */
memcpy(ph,ptemp,len); /* h = temp */
}
memcpy(presult,ph,len);
free(ptemp); /* 確保した領域を解放 */
return 1;
}
数値幅が不明の場合、メモリ確保は malloc 等の動的な方法に限られます。C++や Windows API も含めれば更に多くの方法がありますが、何れにせよ、確保・解放を明示的に指定する必要のある方法です。
この処理だけを見た場合、一応は正しい方法のようにも見えます。しかし、関数は頻繁に呼び出される事が普通なので、メモリの確保・解放も頻繁に繰り返す事になってしまいます。
通常のメモリ確保関数は、多くの要求を満たすため汎用的な作りになっているので、掛かる時間も大きく、プログラムの末端で使用される可能性・頻度が高い関数で使用する事は、あまり好ましくはありません。
ユーティリティーが使用するワークエリア
fixed X ユーティリティー関数では、内部で作業用の領域を確保せず、呼び出し側に求めています。
上記の例で例えると、次のような形になります。
例3.
#include <stdlib.h>
#include "fixedX.h"
#include "fixutil.h"
/*
フィボナッチ数列
(ユーティリティー関数に見立てる)
*/
void fibonacci(unsigned long n,PFIXEDVAL presult,int len,PFIXEDVAL pwork)
{
PFIXEDVAL ph,pl,ptemp;
ph = pwork; pl = ph + len; ptemp = pl + len;
memset(pwork,0,len * 2);
for(*ph = *pl = 1;n;n--){
memcpy(ptemp,ph,len);
fixed_add(ptemp,pl,len); /* temp = h + l */
memcpy(pl,ph,len); /* l = h */
memcpy(ph,ptemp,len); /* h = temp */
}
memcpy(presult,ph,len);
}
#include <stdio.h>
#define BIT 128 /* ここでは 128bit とします */
#define SIZE (BIT / 8)
/*
メイン関数
(ユーティリティー関数を呼び出し、使用する側)
*/
void main(void )
{
FIXEDVAL result[SIZE],workarea[SIZE * 3];
char buf[256];
int i;
for(i = 0;i <= 10;i++){
fibonacci(i,result,SIZE,workarea);
fixedutil_unsigned_num2str(result,SIZE,10,buf,workarea);
printf("%s\n",buf);
}
}
この場合、関数の内部ではなく「呼び出し側がメモリを確保し、関数に与える」という形になります。呼び出し側はサイズが分かっているので、極端に大きな数値幅でなければ、グローバル領域やスタックを使用してもかまいません。動的に確保する場合でも、処理の前後でメモリ確保・解放を行えば良い構成にする事ができますので、効率的な記述が可能です。
ユーティリティー関数は、与えられたワークエリアを関数fibonacci で行われているように分配し、必要な場合はゼロクリアして使用しますので、呼び出し側で初期化する必要が無く、他の関数にも使い回す事ができます。例3 の関数fixedutil_unsigned_num2str は実際のユーティリティー関数で、FIXEDVAL値を文字列に変換します。ここでは、関数fibonacci で使用したワークエリアを使い回しています。
ワークエリアの使い回しができる安全なサイズ
ワークエリアを使い回すには、ワークエリアのサイズを安全な大きさで確保しておく必要があります。リファレンスには必要ワークエリアサイズが記載されていますが、全てのユーティリティー関数の中でワークエリアを一番多く使用するものに合わせれば、どのユーティリティー関数に使っても安全です。
例4.
#include <stdlib.h>
#include "fixedX.h"
#include "fixutil.h"
(関数fibonacci は例3 と同じ)
#include <stdio.h>
#define BIT 128
#define SIZE (BIT / 8)
void main(void )
{
FIXEDVAL result[SIZE];
PFIXEDVAL pworkarea;
char buf[256];
int i;
#define WORKSIZE (SIZE * fixedutil_get_maxworkarea_cnt())
if ((pworkarea = malloc(WORKSIZE)) == 0){
printf("メモリ確保に失敗\n");
return;
}
/*
関数名が長過ぎる、引数が多過ぎるといった使い辛さを感じる場合は、
マクロを利用してください。ただし、使い過ぎるとプログラムが不明瞭
になりますので、ほどほどに適用してください。
*/
#define IxTOA(PSRC,PDEST,RADIX) \
fixedutil_unsigned_num2str((PSRC),SIZE,(RADIX),(PDEST),pworkarea)
for(i = 0;i <= 10;i++){
fibonacci(i,result,SIZE,pworkarea);
IxTOA(result,buf,10);
printf("%s\n",buf);
}
free(pworkarea);
}
関数fixedutil_get_maxworkarea_cnt は、ユーティリティー関数の中で必要とするワークエリアの最大要素数を返します。このように確保したワークエリアは、全てのユーティリティー関数で使用する事ができます。