2

我正在尝试编写一个函数来清理此代码生成的哈希表

/*
* Markov chain random text generator.
*/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "eprintf.h"

enum {
NPREF   = 2,    /* number of prefix words */
NHASH   = 4093, /* size of state hash table array */
MAXGEN  = 10000 /* maximum words generated */
};

typedef struct State State;
typedef struct Suffix Suffix;

struct State {  /* prefix + suffix list */
char*   pref[NPREF];    /* prefix words */
Suffix* suf;            /* list of suffixes */
State*  next;           /* next in hash table */
};

struct Suffix { /* list of suffixes */
char*   word;           /* suffix */
Suffix* next;           /* next in list of suffixes */
};

State* lookup(char *prefix[], int create);
void build(char *prefix[], FILE*);
void generate(int nwords);
void add(char *prefix[], char *word);

State* statetab[NHASH]; /* hash table of states */

char NONWORD[] = "\n";  /* cannot appear as real word */

/* markov main: markov-chain random text generation */
int main(void)
{
int i, nwords = MAXGEN;
char *prefix[NPREF];        /* current input prefix */

int c;
long seed;

setProgName("markov");
seed = time(NULL);

srand(seed);
for (i = 0; i < NPREF; i++) /* set up initial prefix */
    prefix[i] = NONWORD;
build(prefix, stdin);
add(prefix, NONWORD);
generate(nwords);
return 0;
}

const int MULTIPLIER = 31;  /* for hash() */

/* hash: compute hash value for array of NPREF strings */
unsigned int hash(char* s[NPREF])
{
unsigned int h;
unsigned char *p;
int i;

h = 0;
for (i = 0; i < NPREF; i++)
    for (p = (unsigned char *) s[i]; *p != '\0'; p++)
        h = MULTIPLIER * h + *p;
return h % NHASH;
}


/* lookup: search for prefix; create if requested. */
/*  returns pointer if present or created; NULL if not. */
/*  creation doesn't strdup so strings mustn't change later. */
State* lookup(char *prefix[NPREF], int create)
{
int i, h;
State *sp;

h = hash(prefix);
for (sp = statetab[h]; sp != NULL; sp = sp->next) {
    for (i = 0; i < NPREF; i++)
        if (strcmp(prefix[i], sp->pref[i]) != 0)
            break;
    if (i == NPREF)     /* found it */
        return sp;
}
if (create) {
    sp = (State *) emalloc(sizeof(State));
    for (i = 0; i < NPREF; i++)
        sp->pref[i] = prefix[i];
    sp->suf = NULL;
    sp->next = statetab[h];
    statetab[h] = sp;
}
return sp;
}

/* addsuffix: add to state. suffix must not change later */
void addsuffix(State *sp, char *suffix)
{
Suffix *suf;

suf = (Suffix *) emalloc(sizeof(Suffix));
suf->word = suffix;
suf->next = sp->suf;
sp->suf = suf;
}

/* add: add word to suffix list, update prefix */
void add(char *prefix[NPREF], char *suffix)
{
State *sp;

sp = lookup(prefix, 1);  /* create if not found */
addsuffix(sp, suffix);
/* move the words down the prefix */
memmove(prefix, prefix+1, (NPREF-1)*sizeof(prefix[0]));
prefix[NPREF-1] = suffix;
}

/* build: read input, build prefix table */
void build(char *prefix[NPREF], FILE *f)
{
char buf[100], fmt[10];

/* create a format string; %s could overflow buf */
sprintf(fmt, "%%%ds", sizeof(buf)-1);
while (fscanf(f, fmt, buf) != EOF)
    add(prefix, estrdup(buf));
}

/* generate: produce output, one word per line */
void generate(int nwords)
{
State *sp;
Suffix *suf;
char *prefix[NPREF], *w;
int i, nmatch;

for (i = 0; i < NPREF; i++) /* reset initial prefix */
    prefix[i] = NONWORD;

for (i = 0; i < nwords; i++) {
    sp = lookup(prefix, 0);
    if (sp == NULL)
        eprintf("internal error: lookup failed");
    nmatch = 0;
    for (suf = sp->suf; suf != NULL; suf = suf->next)
        if (rand() % ++nmatch == 0) /* prob = 1/nmatch */
            w = suf->word;
    if (nmatch == 0)
        eprintf("internal error: no suffix %d %s", i, prefix[0]);
    if (strcmp(w, NONWORD) == 0)
        break;
    printf("%s\n", w);
    memmove(prefix, prefix+1, (NPREF-1)*sizeof(prefix[0]));
    prefix[NPREF-1] = w;
}
}

这是我到目前为止的清洁功能

/*Clean Function*/
void clean_up(State *sp)
{
State *temp;
Suffix *temp2, temp3;

for(int h = 0; h < NHASH; h++)
{
    for (sp = statetab[h]; sp != NULL; sp = sp->next)
    {
        while(sp->suf != NULL) 
        {
            temp2= sp->suf;
            temp3= *temp2->next;
            free(temp2);
            sp->suf= &temp3;
        }

    }
}
}

我认为我在正确的轨道上,我正在遍历哈希表中的每个索引,然后从一个状态到另一个状态并释放后缀。我不确定如何处理前缀,因为我必须先释放它们才能释放每个状态。任何帮助将不胜感激。

4

2 回答 2

1

在您的代码中,您正在复制到一个 temp3 节点,该节点位于自动内存中(“在堆栈上”),将 sp->suf 指向该内存将(在循环的下一次迭代中)导致 free 被调用地址这个对象(它没有被 malloc 获得,因此不能被 free() 释放)

void clean_up(State *sp)
{
State *temp;
Suffix *temp2, **pp;

for(int h = 0; h < NHASH; h++)
{
    for (sp = statetab[h]; sp != NULL; sp = sp->next)
    {
        for (pp = &sp->suf; *pp; *pp = temp2) 
        {
            temp2 = (*pp)->next;     
            free(*pp);
        }

    }
}
}
于 2012-11-04T19:23:43.633 回答
0

示例代码源自Kernighan 和 Pike 所著的The Practice of Programming中的 Markov 程序,这是一本非常优秀的书。

鉴于您正在尝试清理statetab,主要清理功能不需要任何参数。您必须小心不要直接statetab释放状态,但您确实需要释放链接的辅助状态statetab[i].next

typedef struct State State;
typedef struct Suffix Suffix;

struct State {  /* prefix + suffix list */
char*   pref[NPREF];    /* prefix words */
Suffix* suf;            /* list of suffixes */
State*  next;           /* next in hash table */
};

struct Suffix { /* list of suffixes */
char*   word;           /* suffix */
Suffix* next;           /* next in list of suffixes */
};

State* statetab[NHASH]; /* hash table of states */

static void free_state(State *state);
static void free_suffix(Suffix *suffix);

static void cleanup(void)
{
   for (int i = 0; i < NHASH; i++)
      free_state(statetab[i]);
}

static void free_state(State *state)
{
    if (state != 0)
    {
        for (int i = 0; i < NPREF; i++)
            free(state->pref[i]);
        free_suffix(state->suf);
        if (state->next != 0)
        {
            free_state(state->next);
            free(state->next);
        }
    }
}

static void free_suffix(Suffix *suffix)
{
    if (suffix != 0)
    {
        free(suffix->word);
        free_suffix(suffix->next);
        free(suffix);
    }
}

你看到我是如何free_xxxx()根据结构的设计来设计代码的xxxx吗?

警告讲师:未编译的代码,更少的测试代码。


我从 TPOP 站点挖出代码,并尝试应用它。我对上面的释放代码进行了一些修复(修复了语法错误,检查了 nullfree_state()free_suffix()),但整个代码的设计目的并不是为了释放数据。

有几个问题。首先,一些前缀没有分配(NONWORD)。可以通过测试前缀是否为 NONWORD 来避免释放它们,但这很讨厌。也可以分配这些前缀(将 NONWORD 替换为estrdup(NONWORD))。我认为在另一个地方,某个未分配的指针被隐藏在状态表的前缀中;我在malloc()抱怨“释放未分配的内存”时遇到崩溃(我相信这与“双重释放分配的内存”不同),但我没有设法解决这个问题。

但是,这会变成另一个问题;前缀被重用。也就是说,系统中几乎每个前缀都被用作一个前缀的第二个单词,然后作为下一个前缀的第一个单词。因此,您不能轻易释放前缀。

如果您要设计它以便释放内存,那么您可能会设计它以便有一个“原子”(不可变字符串)系统,这样每个单词都被分配一次并根据需要经常重复使用(参见C Interfaces and Implementations: Techniques for Creating Reusable Code by D Hanson 作为术语的来源)。释放状态表的代码将只关注非字数据。也会有代码来释放完整的原子集。

valgrind我在没有清理的情况下运行了马尔可夫程序;没有内存访问问题,也没有数据泄露;在程序退出时仍然可以访问它。我使用了一个大约 15,000 个单词(以及大约 2900 个不同的单词)的数据文件,统计数据是:

==9610== HEAP SUMMARY:
==9610==     in use at exit: 695,269 bytes in 39,567 blocks
==9610==   total heap usage: 39,567 allocs, 0 frees, 695,269 bytes allocated
==9610== 
==9610== LEAK SUMMARY:
==9610==    definitely lost: 0 bytes in 0 blocks
==9610==    indirectly lost: 0 bytes in 0 blocks
==9610==      possibly lost: 0 bytes in 0 blocks
==9610==    still reachable: 695,269 bytes in 39,567 blocks

所以,你给自己设置了一个有趣的练习。但是,我认为如果不重新设计一些内存分配机制以便可以干净地释放数据,这是无法实现的。

(在 BSD 上,因此在 Mac OS X 上,也有一对函数在<stdlib.h>被调用setprogname()getprogname()中。在 BSD 上,在开始setprogname()之前被自动调用main()argv[0]我相信eprintf.h使用<stdlib.h>这就是为什么问题中的代码使用setProgName()而不是原始代码的原因setprogname()。我选择修复setprogname()eprintf.h以便它接受一个const char *参数,因此与中的声明相匹配<stdlib.h>。)


TPOP 以前位于 http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop但现在都在 (2015-08-10)破碎的。另请参阅关于TPOP的 Wikipedia 。

于 2012-11-04T20:33:27.870 回答