● 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 は、ユーティリティー関数の中で必要とするワークエリアの最大要素数を返します。このように確保したワークエリアは、全てのユーティリティー関数で使用する事ができます。