【问题标题】:Markov Clean Function Trouble马尔可夫清洁功能故障
【发布时间】:2012-11-04 17:42:07
【问题描述】:

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

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

    }
}
}

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

【问题讨论】:

  • 当你对它做的第一件事就是给它赋值statetab[h]时,你为什么要把sp作为参数传递给你的函数呢?如果要清理整个statetab,则不需要sp 作为参数(它可以是内部for 循环的局部变量)。如果您打算使用 sp 作为参数做某事,那么就这样做并且不要将其重用为局部临时变量(虽然它会起作用,但使用相同变量的编码不清楚,尤其是参数, 用于同一功能的不同部分的两种完全不同的目的)。
  • The Practice of Programming 是一本优秀的书,不是吗!

标签: c pointers free markov-chains


【解决方案1】:

在您的代码中,您正在复制到 temp3 节点中,该节点位于自动内存中(“在堆栈上”),将 sp->suf 指向该内存将(在循环的下一次迭代中)导致 free 被调用带有这个对象的地址(它 not 被 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);
        }

    }
}
}

【讨论】:

    【解决方案2】:

    示例代码源自 Kernighan 和 Pike 在The Practice of Programming 中的马尔科夫程序,这是一本非常优秀的书。

    鉴于您正在尝试清理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);
        }
    }
    

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

    Caveat Lector:未编译的代码,更少经过测试的代码。


    我从 TPOP 网站上挖出代码,并尝试应用它。我对上面的释放代码进行了一些修复(修复了语法错误,free_state()free_suffix() 中的空值检查),但整个代码并未设计为允许释放数据。

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

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

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

    我在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 上,&lt;stdlib.h&gt; 中有一对函数称为 setprogname()getprogname()。在 BSD 上,setprogname()main() 开始之前自动调用(我相信argv[0])。eprintf.h 中的声明与&lt;stdlib.h&gt; 中的声明冲突,这可能是问题中的代码使用setProgName() 而不是原来的setprogname() 的原因。我选择修复setprogname()eprintf.h 中,因此它接受了 const char * 参数,因此与 &lt;stdlib.h&gt; 中的声明匹配。)


    TPOP 以前在 http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop 但现在(2015-08-10)两者都坏了。 参见维基百科TPOP

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-31
      • 2012-02-04
      相关资源
      最近更新 更多