Malloc 返回相同的值 - 没有双重释放错误

Malloc returning same value - no double free error

查看最新更新

给定以下函数,注意调用位置free(tmp)

int *power_arr(int *n, int nlength, int exp, int *res_length)
{
    int *tmp, *rt, *bufp;
    int bufp_length, i, dbg_i;

    rt = malloc(sizeof(int) * 1000);
    bufp = malloc(sizeof(int) * 1000);

    if (!rt || !bufp)
    {
        return NULL;
    }

    copy(rt, n, nlength);
    copy(bufp, n, nlength);

    *res_length = bufp_length = nlength;

    while (--exp > 0)
    {
        for (i = *n - 1; i > 0; i--)
        {
            tmp = sum(rt, *res_length, bufp, bufp_length, res_length);

            if (!tmp)
            {
                exit(-1);
            }

            copy(rt, tmp, *res_length);
           //free(tmp); // produces undefined output?
        }

        copy(bufp, rt, *res_length);
        bufp_length = *res_length;
    }

    free(tmp);

    free(bufp);

    return rt;
}

以下主函数的结果:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    int b[] = { 3 };
    int r, i;
    int *rlength, *res;

    r = 0;

    rlength = &r;

    res = power_arr(b, 1, 3, rlength);

    printf("Length = %d\n", *rlength);

    for (i = 0; i < *rlength; i++)
    {   
        printf("i=");
        printf("%d\n", res[i]);
    }

    printf("\n");



    exit(0);
}

是:

Length = 2
i=2
i=7

我对第一种情况的理解是,每个后续 tmp = sum(rt, *res_length, bufp, bufp_length, res_length); 调用都会发生内存泄漏。在这一点上,我决定将调用移至 for 循环内的 free(tmp)。移动后,我注意到输出发生了变化,如下所示:

Length = 4
i=1018670
i=4
i=2
i=7

我可以看到答案从 i[3] 开始。为什么 free(tmp) 调用的移动会导致这种效果?

我的理解是tmpfree()调用后变成了一个悬挂指针。然后,它被重新分配一个由函数 sum() 返回的值 - 通过调用 malloc() 检索该值。哪一点,通过将对 free() 的调用置于其原始位置将发生内存泄漏。由于 tmp 的值会改变,只有 释放 分配给它的最后一个指针。


编辑:

下面是支持函数的代码。

int *pad(int *n, int nlength, int new_length, enum SIDE side)
{
    int i, j;
    int *padded;

    if (nlength < 1 || new_length <= nlength)
    {
        return NULL;
    }

    padded = calloc(new_length, sizeof(int));

    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;
}

int *trim(int *n, int nlength, int *res_length)
{
    int i, j;
    int *res;

    for (i = 0; i < nlength; i++)
    {
        if (n[i] > 0)
        {
            break;
        }
    }

    *res_length = (nlength - i);

    res = malloc(sizeof(int) * (*res_length));

    if (!res)
    {
        return NULL;
    }

    j = 0;

    while (i < nlength)
    {
        res[j++] = n[i++];
    }

    return res;
}

int *sum(int *n, int nlength, int *m, int mlength, int *sum_length)
{
    int i, tmp, carry;
    int *result, *trimmed, *op1, *op2;
    enum SIDE side = LOW;

    if (nlength == mlength)
    {
        op1 = n;
        op2 = m;
    }
    else if (nlength > mlength)
    {
        op1 = n;
        op2 = pad(m, mlength, nlength, side);
    }
    else
    {
        op1 = m;
        op2 = pad(n, nlength, mlength, side);
    }

    result = malloc(sizeof(int) * (MAX(nlength, mlength) + 1));

    if (!op1 || !op2 || !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;
    }

    if (carry > 0)
    {
        result[0] = carry--;
    }

    *sum_length = (MAX(nlength, mlength)) + 1;

    trimmed = trim(result, *sum_length, sum_length);

    free(result);

    if (!trimmed)
    {
        return NULL;
    }

    return trimmed;
}

void copy(int *to, int *from, int length)
{
    int i;

    for (i = 0; i < length; i++)
    {
        to[i] = from[i];
    }
}

更新:

实施第一个 post 中建议的更改后,开始出现 double free 错误,为了调试,我将以下打印语句添加到 power_arr()。下面的输出显示 tmp 被分配的值与 sum() 相同,因为它在初始调用时收到。为什么?

显示调试 printf 语句的更新代码:

    for (i = *n - 1; i > 0; i--)
    {
        tmp = sum(rt, *res_length, bufp, bufp_length, res_length);

        printf("first tmp = %d\n", tmp);

        if (!tmp)
        {
            printf("tmp was null\n");
            exit(-1);
        }

        copy(rt, tmp, *res_length);

        printf("second tmp = %d\n", tmp);\

        if (tmp != NULL)
        {
            printf("freeing tmp\n");
            free(tmp);
            tmp = NULL;
        }

        printf("tmp = %d\n", tmp);
    }

输出:

first tmp = 11227072
second tmp = 11227072
freeing tmp
tmp = 0
first tmp = 11227072 <-- Why has the pointer value not changed?
second tmp = 11227072
freeing tmp <-- Double free now occuring.
*** Error in `./a.out': double free or corruption (fasttop):        0x0000000000ab4fc0 ***
Aborted

更新:

我相信我已将错误追踪到 trim() 函数。我用 循环 post 编辑了函数以连续执行 10 次。正如您在输出中看到的那样,trim() - 在后续调用中调用 malloc()、returns 相同的指针值。然而,每次连续调用 free 都不会触发 double free 错误。为什么会这样?

int main()
{
    int i, j, length;
    int n[] = { 4, 5, 6 };
    int m[] = { 0, 3, 5 };
    int *num;
    int *trimmed, *trimmed_length;

    trimmed_length = &length;

    for (i = 0; i < 10; i++)
    {
        num = (i % 2 == 0) ? n : m;

        trimmed = trim(num, 3, trimmed_length);

        if (!trimmed)
        {
            printf("trimmed was null\n");
            exit(-1);
        }

        for (j = 0; j < *trimmed_length; j++)
        {
            printf("%d", trimmed[j]);
        }   

        printf("\n");

        free(trimmed); 
    }

    exit(0);
} 

int *trim(int *n, int nlength, int *res_length)
{
    int i, j;
    int *res;

    for (i = 0; i < nlength; i++)
    {
        if (n[i] > 0)
        {
            break;
        }
    }

    *res_length = (nlength - i);

    res = malloc(sizeof(int) * (*res_length));

    if (!res)
    {
        return NULL;
    }

    j = 0;

    while (i < nlength)
    {
        res[j++] = n[i++];
    }

    printf("Returned pointer from trim() %d\n", res);

    return res;
}

输出:

Returned pointer from trim() 39534608
456
Returned pointer from trim() 39534608
35
Returned pointer from trim() 39534608
456
Returned pointer from trim() 39534608
35
Returned pointer from trim() 39534608
456
Returned pointer from trim() 39534608
35
Returned pointer from trim() 39534608
456
Returned pointer from trim() 39534608
35
Returned pointer from trim() 39534608
456
Returned pointer from trim() 39534608
35

这似乎也是我最初问题中的行为 - 触发了 double free 错误。为什么在这种特殊情况下没有遇到 double free 错误?

你有一些内存泄漏点和一个完整的错误,这就是释放失败的原因[由于同一指针的双重释放]。

注意:我已经更新了这个答案,但它太大了,放不下,所以我把它作为第二个答案发布了

我已将所有文件合并为一个文件,这样我就可以编译它了[请原谅无偿的样式清理],修复了错误,并注释了所有热点[这个编译,但我没有测试它]:

#include <stdlib.h>
#include <stdio.h>

enum SIDE {
    LOW
};

#define MAX(_x,_y) (((_x) > (_y)) ? (_x) : (_y))

// perform free if pointer is non-null -- set to null afterwards to prevent
// "double free"
#define FREEME(_ptr) \
    do { \
        if (_ptr != NULL) \
            free(_ptr); \
        _ptr = NULL; \
    } while (0)

int *
pad(int *n, int nlength, int new_length, enum SIDE side)
{
    int i,
     j;
    int *padded;

    if (nlength < 1 || new_length <= nlength) {
        return NULL;
    }

    padded = calloc(new_length, sizeof(int));

    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;
}

int *
trim(int *n, int nlength, int *res_length)
{
    int i,
     j;
    int *res;

    for (i = 0; i < nlength; i++) {
        if (n[i] > 0) {
            break;
        }
    }

    *res_length = (nlength - i);

    res = malloc(sizeof(int) * (*res_length));

    if (!res) {
        return NULL;
    }

    j = 0;

    while (i < nlength) {
        res[j++] = n[i++];
    }

    return res;
}

int *
sum(int *n, int nlength, int *m, int mlength, int *sum_length)
{
    int i,
     tmp,
     carry;
    int *result,
    *trimmed,
    *op1,
    *op2;
    int padflg;
    enum SIDE side = LOW;

    // NOTE: this helps us remember whether to free op2 or not
    padflg = 1;

    // 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 = pad(m, mlength, nlength, side);
    }

    // case (3)
    else {
        op1 = m;
        op2 = pad(n, nlength, mlength, side);
    }

    result = malloc(sizeof(int) * (MAX(nlength, mlength) + 1));

    if (!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 pad, 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 = trim(result, *sum_length, sum_length);

    free(result);

    return trimmed;
}

void
copy(int *to, int *from, int length)
{
    int i;

    for (i = 0; i < length; i++) {
        to[i] = from[i];
    }
}

int *
power_arr(int *n, int nlength, int exp, int *res_length)
{
    int *tmp,
    *rt,
    *bufp;
    int bufp_length,
     i;

    // NOTE: rt/bufp are memory leaks -- they are never freed
    rt = malloc(sizeof(int) * 1000);
    bufp = malloc(sizeof(int) * 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;
    }

    copy(rt, n, nlength);
    copy(bufp, n, nlength);

    *res_length = bufp_length = nlength;

    while (--exp > 0) {
        for (i = *n - 1; i > 0; i--) {
            tmp = sum(rt, *res_length, bufp, bufp_length, res_length);

            if (!tmp) {
                exit(-1);
            }

            copy(rt, tmp, *res_length);

            // NOTE: this will now work because of the padflg changes in
            // sum
#if 0
            // free(tmp); // produces undefined output?
#else
            FREEME(tmp);
#endif
        }

        copy(bufp, rt, *res_length);
        bufp_length = *res_length;
    }

    FREEME(bufp);

    return rt;
}

int
main(void)
{
    int b[] = { 3 };
    int r,
     i;
    int *rlength,
    *res;

    r = 0;

    rlength = &r;

    res = power_arr(b, 1, 3, rlength);

    printf("Length = %d\n", *rlength);

    for (i = 0; i < *rlength; i++) {
        printf("i=");
        printf("%d\n", res[i]);
    }

    printf("\n");

    exit(0);
}

更新:我在这里添加第二个答案,因为新的代码示例太大而不适合作为我之前的更新。

我发现了更多问题。如果您重复 运行 原始测试程序,它会在每个 运行 上给出不同的 [不正确] 答案。也就是说,从脚本循环它。

我无法用现有的函数和参数修复剩余的错误。纯粹的复杂性增加了问题。

一旦我意识到你真正想要做的事情(例如多精度数学),我就能够应用几个简化:

我没有到处传递 int *vals, int *len,而是创建了一个 "big number" struct:
- 包含指向数据和长度的指针
- 数据缓冲区的长度可以grow/shrink动态
- 有引用计数

您在 "big endian" 模式下进行数学计算。这使得有必要经常重新分配(例如 padcopy)。这也增加了复杂性。我将其切换为使用 "little endian",这 更易于使用。而且,它也 [相当] 快了一点。

我还在你的幂函数中注意到你使用 sum 来做本质上是 rt += bufp; 的事情。所以,我创建了一个版本 sumeq 直接对 power 的 rt 值进行操作,这进一步简化了事情。

注意:您仍然可以使用大端,但我见过的大多数包都很少使用。不过,create/grow 结构的基本例程与端序无关,你可以创建我创建的小端函数的大端版本 [来自你的大端函数]


这是实际代码。它是可构建的并且 运行 可用。它将测试所有版本,包括您的原始代码[有问题]。

// 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);
}