C语言 程序字数统计过程中的堆阻塞警告

lymnna71  于 2023-01-29  发布在  其他
关注(0)|答案(1)|浏览(125)

我认为有一些与内存和堆损坏相关的问题使我的程序无法正常运行(主要是因为它内部的一些bug)。程序停止运行,或者在退出后崩溃。
我试图学习树是如何工作的,在我的例子中,我必须编写一个交叉引用器来读取文档中的所有单词(在我的例子中,是输入行),以及每个单词出现的行号列表。

foo
bar bar
foo bar

应产生以下产出:

2 foo: [1, 3]
2 bar: [2, 3]

其中[]中的数字表示单词所在的行。
我的代码有两个主要问题:

  • 它只在方括号内打印1,就好像程序从不检查换行符一样
  • 如果我试图运行超过10行的输入,它就会崩溃。没有gdb,它允许我输出所有我想要的行,并且直到它达到10行时才会崩溃:
t
t
t
t
t
quit
   5 t: [1, 1, 1, 1, 1]

当我用gdb运行它时,它给出了以下信息:

(gdb) r
Starting program: C:\...\6.exe
[New Thread 15276.0x14fc]
t
t
t
warning: HEAP[6.exe]:
warning: Heap block at 000001E191B97CA0 modified at 000001E191B97CB6 past requested size of 6

Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x00007ff981f969ff in ntdll!RtlRegisterSecureMemoryCacheCallback () from C:\WINDOWS\SYSTEM32\ntdll.dl
(gdb) bt
#0  0x00007ff981f969ff in ntdll!RtlRegisterSecureMemoryCacheCallback ()
   from C:\WINDOWS\SYSTEM32\ntdll.dll
#1  0x00007ff981f9288a in ntdll!RtlZeroHeap () from C:\WINDOWS\SYSTEM32\ntdll.dll
#2  0x00007ff981f61357 in ntdll!EtwLogTraceEvent () from C:\WINDOWS\SYSTEM32\ntdll.dll
#3  0x00007ff981f95839 in ntdll!RtlRegisterSecureMemoryCacheCallback ()
   from C:\WINDOWS\SYSTEM32\ntdll.dll
#4  0x00007ff981f4de29 in ntdll!EtwLogTraceEvent () from C:\WINDOWS\SYSTEM32\ntdll.dll
#5  0x00007ff981ed24b7 in ntdll!RtlReAllocateHeap () from C:\WINDOWS\SYSTEM32\ntdll.dll
#6  0x00007ff981ed237a in ntdll!RtlReAllocateHeap () from C:\WINDOWS\SYSTEM32\ntdll.dll
#7  0x00007ff97fb71a89 in ucrtbase!_realloc_base () from C:\WINDOWS\System32\ucrtbase.dll
#8  0x00007ff71ff81bbe in addtree ()
#9  0x00007ff71ff81a4e in main ()

我甚至没有输入quit(中断循环的单词),它就停止了,给了我这个警告。
我不知道如何解决这个问题,因为我可能忘记释放一些东西(有一些堆分配),但我不知道问题可能在哪里。
这是密码:

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

#define BUFSIZE     100
#define MAXWORD     100

#define IS_NOISE_WORD(word) \
    (strcmp(word, "a") == 0 || \
    strcmp(word, "an") == 0 || \
    strcmp(word, "the") == 0 || \
    strcmp(word, "and") == 0 || \
    strcmp(word, "or") == 0 || \
    strcmp(word, "in") == 0 || \
    strcmp(word, "of") == 0 || \
    strcmp(word, "to") == 0 || \
    strcmp(word, "is") == 0 || \
    strcmp(word, "are") == 0 || \
    strcmp(word, "was") == 0 || \
    strcmp(word, "were") == 0 || \
    strcmp(word, "be") == 0 || \
    strcmp(word, "been") == 0 || \
    strcmp(word, "being") == 0 || \
    strcmp(word, "have") == 0 || \
    strcmp(word, "has") == 0 || \
    strcmp(word, "had") == 0 || \
    strcmp(word, "having") == 0)
    /* etc. */

#define IS_NOT_NOISE_WORD(word) (!IS_NOISE_WORD(word))

/* the tree node */
struct tnode {
    char *word;             /* points to the text */
    int count;              /* number of occurrences */
    int *lines;             /* lines where the word occurs */
    struct tnode *left;     /* left child */
    struct tnode *right;    /* right child */
};

char buf[BUFSIZE];          /* buffer for ungetch */
int bufp = 0;               /* next free position in buf */

/* char *strdup(char *); */

int getword(char *, int);

struct tnode *addtree(struct tnode *, char *, int);
void tfree(struct tnode *);
void treeprint(struct tnode *);

/* word frequency count */
int main(int argc, char *argv[])
{
    struct tnode *root = NULL;
    char word[MAXWORD];
    int n = 1;  /* number of lines */

    while (getword(word, MAXWORD) != EOF)
    {   
        if (word[0] == '\n')
            n++;
        
        /* if there is a word and it's not a noise */
        if (isalpha(word[0]) && IS_NOT_NOISE_WORD(word) && strcmp(word, "quit") != 0 && strcmp(word, "exit") != 0)
            root = addtree(root, word, n);

        if (!strcmp(word, "quit") || !strcmp(word, "exit"))
            break;
    }

    treeprint(root);
    tfree(root);

    return 0;
}

/* addtree: add a node with the word w at line l, at or below p */
struct tnode *addtree(struct tnode *p, char *w, int l)
{
    int cond;

    /* a new word has arrived */
    if (p == NULL)
    {
        /* make a new node */
        p = malloc(sizeof(struct tnode));
        p->word = strdup(w);
        p->count = 1;
        p->lines = calloc(p->count + 1, sizeof(int));
        p->lines[p->count - 1] = l;
        p->left = p->right = NULL;
    }
    else {
        cond = strcmp(w, p->word);

        if (cond == 0) {
            /* repeated word */
            p->count++;
            p->lines = realloc(p->lines, p->count + 1 * sizeof(int));
            p->lines[p->count - 1] = l;
        }
        else if (cond < 0) {
            /* less than into left subtree */
            p->left = addtree(p->left, w, l);
        }
        else {
            /* greater than into right subtree */
            p->right = addtree(p->right, w, l);
        }
    }

    return p;
}

/* tfree: free a tnode */
void tfree(struct tnode *p)
{
    if (p == NULL)
        return;

    tfree(p->left);
    tfree(p->right);
    free(p);

    if (p->word != NULL) {
        free(p->word);
        p->word = NULL;
    }

    if (p->lines != NULL) {
        free(p->lines);
        p->lines = NULL;
    }
}

/* treeprint: in-order print of tree p */
void treeprint(struct tnode *p)
{
    int i;

    if (p != NULL) {
        treeprint(p->left);
        printf("%4d %s: [%d", p->count, p->word, p->lines[0]);

        for (i = 1; i < p->count; i++)
            printf(", %d", p->lines[i]);

        printf("]\n");
        treeprint(p->right);
    }
}

/* getword: get next word or character from input */
int getword(char *word, int lim)
{
    char *w = word;
    int c, getch(void);
    void ungetch(int);

    int in_comment = 0;     /* 1 if inside a comment */
    int in_pp_line = 0;     /* 1 if inside a preprocessor line */
    int in_string = 0;      /* 1 if inside a string */

    /* skip spaces */
    while (isspace(c = getch()))
        ;

    if (c != EOF)
        *w++ = c;

    /* not underscore, pp line, comment, string */
    if (!isalpha(c) && c != '_' && c != '\"' && c != '#' && c != '/') {
        *w = '\0';
        return c;
    }

    if (c == '\"')
        in_string = 1;

    if (c == '#')
        in_pp_line = 1;

    /* it only checks single line comments for now */
    if (c == '/') {
        if ((c = getch()) == '/')
            in_comment = 1;
        else
            ungetch(c);
    }

    while (--lim > 0)
    {
        c = getch();

        if (in_comment && (c == '\n'))
            in_comment = 0;

        if (in_pp_line && (c == '\n'))
            in_pp_line = 0;

        /* if the char is in a string or in a comment or in a pp line, and is not alphanumeric */
        if (!isalnum(c) && c != '_' && (in_string == 1 || c != '\"') && !in_pp_line && !in_comment)
        {
            ungetch(c);
            break;
        }

        if (c == '/' && *(w - 1) == '/')
            in_comment = 1;

        if (c == '\"')
            in_string = (in_string == 1) ? 0 : 1;

        *w++ = c;
    }

    *w = '\0';

    return word[0];
}

/* get a (possibly pushed-back) character */
int getch(void) {
    return (bufp > 0) ? buf[--bufp] : getchar();
}

/* push character back on input */
void ungetch(int c) {
    if (bufp >= BUFSIZE)
        printf("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

除了崩溃问题之外,我不明白为什么n计数不增加。getword函数根本不返回'\n'吗?

ss2ws0br

ss2ws0br1#

  1. tfree():在指针被释放后,遵从指针是未定义的行为。另外,当你free(p)时,没有必要将p->wordp->lines设置为NULL
void tfree(struct tnode *p) {
    if (!p)
        return;
    tfree(p->left);
    tfree(p->right);
    if (p->word)
        free(p->word);
    if (p->lines)
        free(p->lines);
    free(p);
}
  1. addtree():* 的优先级高于+。它应该是:
p->lines = realloc(p->lines, (p->count + 1) * sizeof(int));

但是当你增加p-〉计数之前的行时你只需要:

p->lines = realloc(p->lines, p->count * sizeof(int));

在对calloc()的调用中也存在类似的逻辑错误。
valgrind在我修复了这两个问题后很高兴,我不能用10行输入重现崩溃。

  1. getword():行号不前进,因为您从最后一行跳过\n
while (isspace(c = getch()))

然而调用方期望单词[0]为“\n”以推进行号n。以下是一个最小修复:

do {
        c = getch();
    } while(c != '\n' && isspace(c));

并且输出现在是:

3 bar: [2, 2, 3]
   2 foo: [1, 3]

也就是说,我建议您让调用者阅读一行fgets(),然后使用getwork()的修订版本将该行拆分为单词。
1.(未固定)getword():除非您真的希望在每种情况下以不同的方式处理输入,否则您3次单独调用getch()是有问题的。

  1. addtree():您当前记录了给定单词的重复行,但似乎希望后续重复行无效。此外,可能还不如使用malloc()而不是calloc(),因为您在后面显式设置了p->lines。对于sizeof(),首选使用变量而不是类型。
struct tnode *addtree(struct tnode *p, char *w, int l) {
    if (!p) {
        /* make a new node */
        p = malloc(sizeof *p);
        p->word = strdup(w);
        p->count = 1;
        p->lines = malloc(sizeof *p->lines);
        p->lines[p->count - 1] = l;
        p->left = NULL;
        p->right = NULL;
        return p;
    }
    int cond = strcmp(w, p->word);
    if(cond < 0)
        p->left = addtree(p->left, w, l);
    else if(!cond && l != p->lines[p->count - 1]) {
        p->count++;
        p->lines = realloc(p->lines, p->count * sizeof(int));
        p->lines[p->count - 1] = l;
    } else if(cond > 0)
        p->right = addtree(p->right, w, l);
    return p;
}

并且输出现在是:

2 bar: [2, 3]
   2 foo: [1, 3]

1.(未修复)如果realloc()失败,p = realloc(p, ...)泄漏p。

int *tmp = realloc(p->lines, (p->count + 1) * sizeof(int));
    if(!tmp) {
        // handle error
        return NULL;
    }
    p->lines = tmp;

1.(未修复)malloc()calloc()strdup()可能会失败并返回NULL。您需要检查:

p = malloc(sizeof(struct tnode));
        if(!p) {
            // handle error
            return NULL;
        }

相关问题