更新:我在这里添加第二个答案,因为新的代码示例太大,不适合作为我之前的更新。
我发现了更多问题。如果您重复运行原始测试程序,它会在每次运行时给出不同的 [不正确] 答案。也就是说,从脚本循环它。
我无法使用现有函数和参数修复剩余的错误。纯粹的复杂性增加了问题。
一旦我意识到你真正想要做的是什么(例如多精度数学),我就能够应用一些简化:
我没有到处传递int *vals, int *len,而是创建了一个“大数字”struct:
- 包含指向数据和长度的指针
- 数据缓冲区的长度可以动态增长/收缩
- 有引用计数
您在“大端”模式下进行数学运算。这使得有必要经常重新分配(例如pad 和copy)。这也增加了复杂性。我将其切换为使用“little endian”, 更易于使用。而且,它也有点[相当]快。
我还在你的幂函数中注意到你使用sum 来做本质上是rt += bufp; 的事情。所以,我创建了一个版本sumeq,直接对power的rt值进行操作,进一步简化了事情。
注意:您仍然可以使用大端,但我见过的大多数包都很少使用。尽管如此,创建/增长结构的基本例程与字节序无关,您可以创建我创建的小字节序函数的大字节序版本[来自您的大字节序函数]
这是实际代码。它是可构建和可运行的。它将测试所有版本,包括您的原始代码 [有问题]。
// badfree -- test program
#define TSTDEFALL
// badfree/badfree.h -- badfree control
#ifndef _badfree_badfree_h_
#define _badfree_badfree_h_
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef DEBUG
#define dbgprtf(_fmt...) printf(_fmt)
#define dbgexec(_exec) _exec
#else
#define dbgprtf(_fmt...) /**/
#define dbgexec(_exec) /**/
#endif
#define dbgleshow(_num,_who) dbgexec(leshow(_num,0,_who))
#define sysfault(_fmt...) \
do { \
printf(_fmt); \
fflush(stdout); \
_sysfault(); \
} while (0)
typedef unsigned int u32;
#if BIGDIG == 1
typedef char bigdig_t;
#elif BIGDIG == 2
typedef short bigdig_t;
#else
typedef int bigdig_t;
#endif
typedef bigdig_t *bigdig_p;
typedef const bigdig_t *bigdig_pc;
// multiprecision number control
struct bignum {
u32 num_opts; // options
const char *num_tag; // symbol name
int num_refcnt; // reference count
int num_curlen; // current length
int num_maxlen; // maximum length
bigdig_p num_base; // pointer to vector
};
typedef struct bignum bgn_t;
typedef bgn_t *bgn_p;
typedef const bgn_t *bgn_pc;
// num_opts definitions
#define BGNASTRUCT (1 << 0) // struct was allocated
#define BGNABASE (1 << 1) // num_base was allocated
#define BGNINITAV (1 << 2) // bgninit -- init values
#define BGNINITDUP (1 << 3) // bgninit -- init values
#define BGNTRIM (1 << 4) // trim number
#define BGNRAW (1 << 5) // output number in raw order
#define BGNACQUIRE(_num) \
bgnacquire(_num)
#define BGNRELEASE(_num) \
_num = bgnrelease(_num)
#define BGNINIT(_sym,_opt,_len...) \
_sym = bgninit(#_sym,_opt,_len)
#define BGNASSERT(_num,_opt) \
do { \
if (((_num)->num_opts & _opt) == (_opt)) \
break; \
sysfault("BGNASSERT: failure -- num=%p num_opts=%8.8X opt=%8.8X\n", \
num,num->num_opts,_opt); \
} while (0)
#define BGNASSERT_NOT(_num,_opt) \
do { \
if (((_num)->num_opts & _opt) != (_opt)) \
break; \
sysfault("BGNASSERT_NOT: failure -- num=%p num_opts=%8.8X opt=%8.8X\n", \
num,num->num_opts,_opt); \
} while (0)
enum SIDE {
LOW
};
#define MAX(_x,_y) (((_x) > (_y)) ? (_x) : (_y))
#define MIN(_x,_y) (((_x) < (_y)) ? (_x) : (_y))
// perform free if pointer is non-null -- set to null afterwards to prevent
// "double free" and use of pointer after it has been freed
#define FREEME(_ptr) \
do { \
if (_ptr != NULL) \
free(_ptr); \
_ptr = NULL; \
} while (0)
// test control
struct tstctl {
int tst_allow; // allow test to be performed
const char *tst_tag; // name of test
void (*tst_fnc)(void); // test function
};
typedef struct tstctl tstctl_t;
typedef tstctl_t *tstctl_p;
typedef const tstctl_t *tstctl_pc;
#define FORTSTALL(_tst) \
_tst = tstlist; _tst->tst_tag != NULL; ++_tst
int
main(int argc, char **argv);
void
dotests(void);
void
dotest(tstctl_p tst);
void
usage(void);
void
becopy(bigdig_t *to, const bigdig_t *from, int length);
bigdig_t *
bepad(const bigdig_t *n, int nlength, int new_length, enum SIDE side);
bigdig_t *
betrim(const bigdig_t *n, int *res_length);
bigdig_t *
besum(bigdig_t *n, int nlength, bigdig_t *m, int mlength,
int *sum_length);
bigdig_t *
be_power_arr(const bigdig_t *n, int nlength, int exp, int *res_length);
void
betest_orig(void);
void
betest_trim(void);
void
betest_show(bigdig_pc num,int len,const char *sym);
bgn_p
bgninit(const char *tag,u32 opt,int maxlen,...);
bgn_p
bgnacquire(bgn_p num);
bgn_p
bgnrelease(bgn_p num);
void
bgngrow(bgn_p num,int newlen);
void
bgnshrink(bgn_p num);
void
lecopy(bgn_p to,bgn_pc from);
bgn_p
lepad(bgn_pc num,int newlen);
void
letrim(bgn_p num);
int
_letrim(bgn_pc num);
void
leshow(bgn_pc num,u32 opt,const char *who);
void
lesumeq(bgn_p result,bgn_p op2);
bgn_p
le_power_arr_eq(bgn_pc num,int exp);
bgn_p
lesumrt(bgn_p n,bgn_p m);
bgn_p
le_power_arr_rt(bgn_pc n, int exp);
void
letest_lesumrt(void);
void
letest_lesumeq(void);
void
_sysfault(void);
void
reverse(bigdig_p arr, int length);
#define TSTDEF(_on,_fnc) \
{ .tst_allow = _on, .tst_tag = #_fnc, .tst_fnc = _fnc }
#ifdef TSTDEFALL
tstctl_t tstlist[] = {
TSTDEF(1,betest_orig),
TSTDEF(0,betest_trim),
TSTDEF(1,letest_lesumrt),
TSTDEF(1,letest_lesumeq),
{ .tst_tag = NULL }
};
#endif
#endif
// badfree/bemath -- big endian math
void
becopy(bigdig_t *to, const bigdig_t *from, int length)
{
int i;
for (i = 0; i < length; i++)
to[i] = from[i];
}
bigdig_t *
bepad(const bigdig_t *n, int nlength, int new_length, enum SIDE side)
{
int i;
int j;
bigdig_t *padded;
if (nlength < 1 || new_length <= nlength) {
sysfault("bepad: length fault -- nlength=%d new_length=%d\n",
nlength,new_length);
return NULL;
}
padded = calloc(new_length,sizeof(bigdig_t));
if (!padded)
return NULL;
if (side == LOW) {
j = new_length - 1;
for (i = (nlength - 1); i >= 0; i--)
padded[j--] = n[i];
}
else {
j = 0;
for (i = 0; i < nlength; i++)
padded[j++] = n[i];
}
return padded;
}
bigdig_t *
betrim(const bigdig_t *n, int *res_length)
{
int i;
int j;
int nlength;
bigdig_t *res;
nlength = *res_length;
for (i = 0; i < nlength; i++) {
if (n[i] > 0)
break;
}
nlength -= i;
*res_length = nlength;
res = malloc(sizeof(bigdig_t) * nlength);
if (!res) {
sysfault("betrim: null malloc\n");
return NULL;
}
j = 0;
for (; i < nlength; ++i, ++j)
res[j] = n[i];
return res;
}
bigdig_t *
besum(bigdig_t *n, int nlength, bigdig_t *m, int mlength,
int *sum_length)
{
int i;
int tmp;
int carry;
bigdig_t *result;
bigdig_t *trimmed;
bigdig_t *op1;
bigdig_t *op2;
int padflg;
enum SIDE side = LOW;
// NOTE: here op2 comes from _caller_ -- so do _not_ free it in this
// function -- _this_ is the cause of the bug
// case (1)
if (nlength == mlength) {
op1 = n;
op2 = m;
padflg = 0;
}
// NOTE: here op2 comes from _pad_ -- so we do _want_ to free it so it
// doesn't leak
// case (2)
else if (nlength > mlength) {
op1 = n;
op2 = bepad(m, mlength, nlength, side);
padflg = 1;
}
// case (3)
else {
op1 = m;
op2 = bepad(n, nlength, mlength, side);
padflg = 2;
}
result = malloc(sizeof(bigdig_t) * (MAX(nlength, mlength) + 1));
if (!op1 || !op2 || !result) {
sysfault("besum: null fault -- padflg=%d op1=%p op2=%p result=%p\n",
padflg,op1,op2,result);
if (padflg)
FREEME(op2);
FREEME(result);
return NULL;
}
carry = 0;
for (i = (MAX(nlength, mlength)) - 1; i >= 0; i--) {
tmp = op1[i] + op2[i] + carry;
if (carry > 0)
carry = 0;
if (tmp >= 10) {
carry = tmp / 10;
tmp = tmp % 10;
}
result[i + 1] = tmp;
}
// NOTE: we want to free op2 for case (2)/(3) but we didn't remember
// how we got it: (1) means no free, (2)/(3) means free
// only free if this if we called bepad, and _not_ if this pointer belongs
// to caller
if (padflg)
FREEME(op2);
if (carry > 0)
result[0] = carry;
*sum_length = (MAX(nlength, mlength)) + 1;
trimmed = betrim(result, sum_length);
FREEME(result);
return trimmed;
}
bigdig_t *
be_power_arr(const bigdig_t *n, int nlength, int exp, int *res_length)
{
bigdig_t *tmp;
bigdig_t *rt;
bigdig_t *bufp;
int bufp_length;
int i;
// NOTE: rt/bufp are memory leaks -- they are never freed
rt = malloc(sizeof(bigdig_t) * 1000);
bufp = malloc(sizeof(bigdig_t) * 1000);
// NOTE: this is a memory leak -- if one is null, but the other is non-null,
// you must free the non-null one or it leaks
if (!rt || !bufp) {
FREEME(rt);
FREEME(bufp);
return NULL;
}
becopy(rt, n, nlength);
becopy(bufp, n, nlength);
*res_length = bufp_length = nlength;
while (--exp > 0) {
for (i = *n - 1; i > 0; i--) {
tmp = besum(rt, nlength, bufp, bufp_length, res_length);
if (tmp == NULL)
sysfault("be_power_arr: null besum return\n");
nlength = *res_length;
becopy(rt, tmp, nlength);
// NOTE: this will now work because of the padflg changes in
// besum
#if 0
// free(tmp); // produces undefined output?
#else
FREEME(tmp);
#endif
}
becopy(bufp, rt, *res_length);
bufp_length = *res_length;
}
FREEME(bufp);
return rt;
}
// badfree/betest -- big endian tests
void
betest_orig(void)
{
#if 1
bigdig_t b[] = { 3 };
#else
const bigdig_t b[] = { 3 };
#endif
int rlength;
int exp;
bigdig_t *res;
exp = 3;
printf("\n");
printf("betest_orig: exp=%d\n",exp);
betest_show(b,1,"b");
rlength = 0;
res = be_power_arr(b, 1, exp, &rlength);
betest_show(res,rlength,"res");
FREEME(res);
}
void
betest_trim(void)
{
int i;
int j;
int length;
const bigdig_t n[] = { 4, 5, 6 };
const bigdig_t m[] = { 0, 3, 5 };
const bigdig_t *num;
bigdig_t *trimmed;
printf("\n");
printf("betest_trim:\n");
for (i = 0; i < 10; i++) {
num = (i % 2 == 0) ? n : m;
length = 3;
trimmed = betrim(num, &length);
if (!trimmed)
sysfault("betest_trim: trimmed was null\n");
printf("pass %d: num=%p trimmed=%p\n",i,num,trimmed);
for (j = 0; j < length; j++)
printf(" %d", trimmed[j]);
printf("\n");
FREEME(trimmed);
}
}
// betest_show -- show number
void
betest_show(bigdig_pc num,int len,const char *sym)
{
int i;
printf(" sym %s length %d --",sym,len);
for (i = 0; i < len; i++)
printf(" %d",num[i]);
printf("\n");
}
// badfree/bgn -- big number control
// bgninit -- create new number
bgn_p
bgninit(const char *tag,u32 opt,int maxlen,...)
// opt -- options (BGNINIT* -- has initializer data)
// maxlen -- length of number
{
va_list ap;
int i;
bgn_pc from;
bgn_p num;
va_start(ap,maxlen);
num = calloc(1,sizeof(bgn_t));
opt |= BGNASTRUCT;
opt |= BGNABASE;
num->num_opts = opt;
num->num_tag = tag;
num->num_refcnt = 1;
if (maxlen <= 0)
maxlen = 1;
from = NULL;
if (opt & BGNINITDUP) {
from = va_arg(ap,bgn_pc);
maxlen = MAX(maxlen,from->num_curlen);
}
num->num_maxlen = maxlen;
num->num_base = calloc(maxlen,sizeof(bigdig_t));
// initialize from varargs
if (opt & BGNINITAV) {
for (i = 0; i < maxlen; ++i)
num->num_base[i] = va_arg(ap,int);
num->num_curlen = maxlen;
}
// initialize by cloning data
if (opt & BGNINITDUP) {
maxlen = from->num_curlen;
for (i = 0; i < maxlen; ++i)
num->num_base[i] = from->num_base[i];
num->num_curlen = maxlen;
}
va_end(ap);
return num;
}
// bgnacquire -- increment reference count
bgn_p
bgnacquire(bgn_p num)
{
num->num_refcnt += 1;
return num;
}
// bgnrelease -- decrement reference count and deallocate
bgn_p
bgnrelease(bgn_p num)
{
if (--num->num_refcnt == 0) {
if (num->num_opts & BGNABASE)
FREEME(num->num_base);
if (num->num_opts & BGNASTRUCT)
FREEME(num);
// this zaps caller's pointer
num = NULL;
}
return num;
}
// bgngrow -- grow allocated number to given length
void
bgngrow(bgn_p num,int newlen)
{
int growlen;
int maxlen;
int i;
BGNASSERT(num,BGNABASE);
maxlen = num->num_maxlen;
growlen = newlen - maxlen;
if (growlen > 0) {
maxlen += growlen;
num->num_base = realloc(num->num_base,sizeof(bigdig_t) * maxlen);
// zero extend the new area
for (i = num->num_maxlen; i < maxlen; ++i)
num->num_base[i] = 0;
num->num_maxlen = maxlen;
}
}
// bgnshrink -- shrink allocated number to current length
void
bgnshrink(bgn_p num)
{
int curlen;
BGNASSERT(num,BGNABASE);
curlen = num->num_curlen;
if (num->num_maxlen > curlen) {
num->num_base = realloc(num->num_base,sizeof(bigdig_t) * curlen);
num->num_maxlen = curlen;
}
}
// badfree/lecom -- little endian math common
// lecopy -- copy big number
void
lecopy(bgn_p to,bgn_pc from)
{
int newlen;
int i;
dbgprtf("lecopy: ENTER\n");
dbgleshow(to,"lecopy");
dbgleshow(from,"lecopy");
newlen = from->num_curlen;
bgngrow(to,newlen);
for (i = 0; i < newlen; ++i)
to->num_base[i] = from->num_base[i];
to->num_curlen = newlen;
dbgleshow(to,"lecopy");
dbgprtf("lecopy: EXIT\n");
}
// lepad -- clone and pad number
bgn_p
lepad(bgn_pc num,int newlen)
{
int i;
int curlen;
bgn_p padded;
curlen = num->num_curlen;
#if 0
if ((curlen < 1) || (newlen <= curlen)) {
sysfault("lepad: length fault -- curlen=%d newlen=%d\n",curlen,newlen);
return NULL;
}
#endif
BGNINIT(padded,0,newlen);
if (!padded) {
sysfault("lepad: bgninit returned null\n");
return NULL;
}
// copy existing digits
for (i = 0; i < curlen; ++i)
padded->num_base[i] = num->num_base[i];
// zero extend the larger number
for (; i < newlen; ++i)
padded->num_base[i] = 0;
return padded;
}
// letrim -- get rid of leading zeroes by adjusting current length
void
letrim(bgn_p num)
{
num->num_curlen = _letrim(num);
}
// _letrim -- get rid of leading zeroes by adjusting current length
// RETURNS: trimmed length
int
_letrim(bgn_pc num)
{
int i;
int curlen;
curlen = num->num_curlen;
for (i = curlen - 1; i >= 0; --i) {
if (num->num_base[i] > 0)
break;
}
if (i <= 0)
i = 1;
return i;
}
// leshow -- show number
void
leshow(bgn_pc num,u32 opt,const char *who)
{
int curlen;
int i;
if (opt & BGNTRIM)
curlen = _letrim(num);
else
curlen = num->num_curlen;
if (who != NULL)
printf("%s: ",who);
printf("sym=%s ref=%d len=%d/%d",
num->num_tag,num->num_refcnt,curlen,num->num_maxlen);
printf(" trim=%s order=%s",
(opt & BGNTRIM) ? "yes" : "no",
(opt & BGNRAW) ? "raw" : "flip");
printf("\n");
if (who != NULL)
printf("%s:",who);
printf(" ");
if (opt & BGNRAW) {
for (i = 0; i < curlen; ++i)
printf(" %d",num->num_base[i]);
}
else {
for (i = curlen - 1; i >= 0; --i)
printf(" %d",num->num_base[i]);
}
printf("\n");
}
// badfree/lesumeq -- little endian sum / power (in-place)
// lesumeq -- do x += y
void
lesumeq(bgn_p result,bgn_p op2)
{
int op2len;
int i;
int tmp;
int carry;
int maxlen;
int minlen;
op2len = op2->num_curlen;
tmp = result->num_curlen;
maxlen = MAX(tmp,op2len);
minlen = MIN(tmp,op2len);
bgngrow(result,maxlen + 1);
carry = 0;
i = 0;
for (; i < minlen; ++i) {
tmp = result->num_base[i] + op2->num_base[i] + carry;
if (tmp >= 10) {
carry = tmp / 10;
tmp %= 10;
}
else
carry = 0;
result->num_base[i] = tmp;
}
++maxlen;
for (; i < maxlen; ++i) {
if (! carry)
break;
tmp = result->num_base[i] + carry;
if (tmp >= 10) {
carry = tmp / 10;
tmp %= 10;
}
else
carry = 0;
result->num_base[i] = tmp;
}
result->num_curlen = maxlen;
}
// le_power_arr_eq -- raise number to power
bgn_p
le_power_arr_eq(bgn_pc num,int exp)
{
bgn_p rtpwr;
bgn_p bufp;
int icur;
int ilim;
BGNINIT(rtpwr,BGNINITDUP,1000,num);
BGNINIT(bufp,BGNINITDUP,1000,num);
ilim = num->num_base[0];
ilim -= 1;
while (--exp > 0) {
for (icur = 0; icur < ilim; ++icur)
lesumeq(rtpwr,bufp);
lecopy(bufp,rtpwr);
}
BGNRELEASE(bufp);
return rtpwr;
}
// badfree/lesumrt -- little endian sum / power (alloc/return mode)
bgn_p
lesumrt(bgn_p n,bgn_p m)
{
int i;
int tmp;
int carry;
int maxlen;
bgn_p rtsum;
bgn_p op1;
bgn_p op2;
dbgprtf("lesumrt: ENTER\n");
dbgleshow(n,"lesumrt");
dbgleshow(m,"lesumrt");
// case (1)
if (n->num_curlen == m->num_curlen) {
op1 = BGNACQUIRE(n);
op2 = BGNACQUIRE(m);
}
// case (2)
else if (n->num_curlen > m->num_curlen) {
op1 = BGNACQUIRE(n);
op2 = lepad(m,n->num_curlen);
}
// case (3)
else {
op1 = BGNACQUIRE(m);
op2 = lepad(n,m->num_curlen);
}
maxlen = MAX(n->num_curlen,m->num_curlen);
dbgprtf("lesumrt: PAD maxlen=%d\n",maxlen);
BGNINIT(rtsum,0,maxlen + 1);
carry = 0;
for (i = 0; i < maxlen; ++i) {
tmp = op1->num_base[i] + op2->num_base[i] + carry;
if (tmp >= 10) {
carry = tmp / 10;
tmp %= 10;
}
else
carry = 0;
rtsum->num_base[i] = tmp;
}
rtsum->num_base[i] += carry;
rtsum->num_curlen = maxlen + 1;
BGNRELEASE(op1);
BGNRELEASE(op2);
dbgleshow(rtsum,"lesumrt");
dbgprtf("lesumrt: EXIT\n");
return rtsum;
}
bgn_p
le_power_arr_rt(bgn_pc n, int exp)
{
bgn_p tmp;
bgn_p rtpwr;
bgn_p bufp;
int icur;
int ilim;
dbgprtf("le_power_arr_rt: ENTER\n");
BGNINIT(rtpwr,BGNINITDUP,1000,n);
BGNINIT(bufp,BGNINITDUP,1000,n);
ilim = n->num_base[0];
ilim -= 1;
while (--exp > 0) {
for (icur = 0; icur < ilim; ++icur) {
tmp = lesumrt(rtpwr,bufp);
if (tmp == NULL)
sysfault("le_power_arr_rt: null lesumrt return\n");
lecopy(rtpwr,tmp);
BGNRELEASE(tmp);
}
lecopy(bufp,rtpwr);
}
BGNRELEASE(bufp);
dbgprtf("le_power_arr_rt: EXIT\n");
return rtpwr;
}
// badfree/letest -- little endian tests
void
letest_lesumrt(void)
{
bgn_p b;
int exp;
bgn_p res;
exp = 3;
printf("\n");
printf("letest_lesumrt: exp=%d\n",exp);
BGNINIT(b,BGNINITAV,1,3);
leshow(b,0,"letest_lesumrt");
res = le_power_arr_rt(b,exp);
leshow(res,0,"letest_lesumrt");
BGNRELEASE(res);
BGNRELEASE(b);
}
void
letest_lesumeq(void)
{
bgn_p b;
int exp;
bgn_p res;
exp = 3;
printf("\n");
printf("letest_lesumeq: exp=%d\n",exp);
BGNINIT(b,BGNINITAV,1,3);
leshow(b,0,"letest_lesumeq");
res = le_power_arr_eq(b,exp);
leshow(res,0,"letest_lesumeq");
BGNRELEASE(res);
BGNRELEASE(b);
}
// badfree/util -- utility functions
void
_sysfault(void)
{
exit(1);
}
void
reverse(bigdig_p arr, int length)
{
int lhs;
int rhs;
bigdig_t tmp;
for (lhs = 0, rhs = length - 1; lhs < rhs; ++lhs, --rhs) {
tmp = arr[lhs];
arr[lhs] = arr[rhs];
arr[rhs] = tmp;
}
}
int opt_fork;
int opt_T;
// main -- main program
int
main(int argc, char **argv)
{
char *cp;
tstctl_p tst;
int tstno;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'f':
opt_fork = 1;
break;
case 'h':
usage();
break;
case 'T':
opt_T = atoi(cp + 2);
break;
default:
usage();
break;
}
}
if (opt_T <= 0)
opt_T = 1;
for (FORTSTALL(tst))
tst->tst_allow = (argc <= 0);
for (; argc > 0; --argc, ++argv) {
cp = *argv;
for (FORTSTALL(tst)) {
if (strcmp(tst->tst_tag, cp) == 0)
tst->tst_allow = 1;
}
}
for (tstno = 0; tstno < opt_T; ++tstno)
dotests();
return 0;
}
// dotests -- perform tests
void
dotests(void)
{
tstctl_p tst;
pid_t pid;
int status;
for (FORTSTALL(tst)) {
if (!tst->tst_allow)
continue;
if (!opt_fork) {
dotest(tst);
continue;
}
pid = fork();
if (pid) {
waitpid(pid, &status, 0);
continue;
}
dotest(tst);
exit(0);
}
}
// dotest -- perform test
void
dotest(tstctl_p tst)
{
tst->tst_fnc();
}
// usage -- show usage
void
usage(void)
{
tstctl_pc tst;
printf("usage: [options] [test names]\n");
printf("\n");
printf("options:\n");
printf(" -f -- run tests in forked child\n");
printf(" -T<repeat> -- test repeat count\n");
printf("\n");
printf("tests:\n");
for (FORTSTALL(tst))
printf(" %s\n", tst->tst_tag);
exit(1);
}