动态内存分配和释放问题

4

我有一段代码,我运行了几次:

void svnViewStatus()
{
    FILE *file;
    int i, j, k, q=0, ok;
    char mystring[100000], *vect[100000], *var, last[12], first[12];
    file = fopen("db_svnViewStatus.txt", "r");
    if(file == NULL)
    {
        printf("error");
    }
    else
    {
        i=1;
        while(fgets(mystring, 100000, file))
        {
            if( i > 1) 
            {
                j=1;
                var = strtok(mystring, ";");
                while(var != NULL)
                {
                    if(j==3)
                    {
                        if(i == 2)
                        {
                            strcpy(first, var);
                        }
                        strcpy(last, var);
                    }
                    if(j == 2)
                    {
                        if(q != 0)
                        {
                            ok=1;
                            for(k=0; k<q; k=k+2)
                            {
                                if(strcmp(vect[k], var) == 0)
                                {
                                    *vect[k+1]++;
                                    ok=0;
                                }
                            }
                            if(ok == 1)
                            {
                                vect[q] = malloc(strlen(var)+1);
                                strcpy(vect[q], var);
                                vect[q+1] = malloc(sizeof(int));
                                *vect[q+1] = 1;
                                q = q+2;
                            }
                        }
                        else
                        {
                            vect[q] = malloc(strlen(var)+1);
                            strcpy(vect[q], var);
                            vect[q+1] = malloc(sizeof(int));
                            *vect[q+1] = 1;
                            q = q+2;
                        }
                    }
                    j++;
                    var = strtok(NULL, ";");
                }
            }
            i++;
        }
    }
    fclose(file);
    printf("nr: %d \n", i-2);
    printf("first: %s \n", first);
    printf("last: %s \n", last);
    for(i=0; i<q; i = i+2)
    {
        printf("User %s: %d \n", *vect[i], *vect[i+1]);
    }
    for(i=0; i<q; i=i+1)
    {
         free(vect[i]);
    }

}

在db_svnViewStatus.db中我有:

NumeRepo:CitateWoodyAllen;DataCreat:12 Nov 2011;Detinator:Ioana;Descriere:Citate ale faimosului regizor Woody Allen
1;Ioana;12 Nov 2011;Woody Allen;What if everything is an illusion and nothing exists? In that case, I definitely overpaid for my carpet.
2;Mihai;12 Nov 2011;Woody Allen;The lion and the calf shall lie down together but the calf won't get much sleep
3;Mihai;13 Nov 2011;Woody Allen;Eighty percent of success is showing up
4;Cristi;23 Nov 2011;Woody Allen;It is impossible to travel faster than the speed of light, and certainly not desirable, as one's hat keeps blowing off
5;Ioana;25 Nov 2011;Woody Allen;I had a terrible education. I attended a school for emotionally disturbed teachers.
6;Cristi;25 Nov 2011;Woody Allen;I will not eat oysters. I want my food dead. Not sick. Not wounded. Dead.

但是我得到了这个:

"检测到堆损坏:在0x000032E90处的普通块(#54)之后。 CRT检测到应用程序在堆缓冲区结束后写入内存。"

需要帮助吗?

此外,我在分配内存后应该使用free吗?为什么?


你分配了vect[q]vect[q+1],但你释放了vect[0]vect[q-1]。你是否分配了你要释放的资源,并释放了你所分配的资源? - Jonathan Leffler
你的编译器有以下任何警告吗?xx.c:43: warning: value computed is not used / xx.c:78: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’? 如果有,那就修复它们。如果没有,请将编译器警告级别提高 - 或者使用一个会抱怨的编译器。你学过 struct 吗?如果是,请使用它。如果没有,那么你所做的事情只有微不足道的借口。(GCC 4.2.1 / LLVM 在 MacOS X 10.7.2 上使用 -Wall -Wextra 产生了上述警告。) - Jonathan Leffler
哪里?我不在任何地方使用 %s 与 int。哪一行?是的,我学过结构体...但是,如果我可以直接处理数据,为什么要使用结构体呢? - FinalDestiny
主要问题在我之前评论中的“第43行”。您需要if (strcmp(vect[k], var) == 0) { (*vect[k+1])++; ok = 0; }。至少GCC会警告您有麻烦。您使用了*vect[k+1]++;,这会增加指针而不是指向的整数。然后一切都失控了。请注意您的编译器-并确保您提高警告级别,以便获得所需的帮助。 - Jonathan Leffler
%sint问题在printf()接近结尾处;您传递的是*vect[i],它是一个字符(升级为int,因此出现警告),而不是vect[i]。使用结构体可以节省空间和烦恼。您有一个char *数组,其中将偶数索引视为字符串,奇数索引视为指向int的指针。最好使用struct data { char *name; int count; }的数组。 - Jonathan Leffler
6个回答

6

您应该为终止零分配内存,如下所示:

vect[q] = malloc(strlen(var)+1);

如果您没有分配额外的字节,strcpy将在已分配块的末尾之后写入,导致堆损坏。

尝试了那个,现在程序崩溃了。如果我去掉释放的东西,它就不会崩溃。 - FinalDestiny
@FinalDestiny vect[q+1] = 1; 应为 *vect[q+1] = 1; - Sergey Kalinichenko
@FinalDestiny,您能否更新问题并展示更多错误附近的代码行?您发布的代码行中没有其他问题。 - Sergey Kalinichenko
@FinalDestiny在使用vect之前应该将其清零,像这样:memset(vect, 0, sizeof(vect)) - Sergey Kalinichenko
程序仍然崩溃,非常遗憾 :-( - FinalDestiny

4

有两个问题。第一个是你没有为字符串分配足够的内存。你需要额外的一个字节来放置空终止符。

另一个问题是你将 vect [ q + 1 ] 设置为指针,然后用值 1 覆盖该指针。当你尝试释放它时,你正在尝试释放内存位置 1 上的内存。你需要改变:

vect[q+1] = 1;

to

*(vect [ q + 1 ]) = 1;

@FinalDestiny:修复了uer1118321的第二个bug后仍然崩溃?很好发现,我完全忽略了它。天啊。 :) - sarnold
你的函数在 malloc 和 free 之间还做了什么? - user1118321
它读取一个文件并使用其中的数据,将一些用户及其执行的操作数量添加到vect数组中。基本上,每次读取新用户时,我只需创建一个新条目。对于已经存在的用户,我使用*vect[k+1]++代码。 - FinalDestiny
vect[i] 这个写法很令人困惑。你能想到更好的写法吗?vect 是一个指向字符的指针数组。vect [ i ] 是一个指向字符的指针。vect [ i ] 是一个字符。所以你要打印一个字符,而不是打印由 vect [ i ] 指向的字符串。你可能在 *vect[k+1]++ 这一行中有类似的错误。 - user1118321

1

这里你没有分配足够的内存:

vect[q] = malloc(strlen(var));

strlen(3) 只报告了字符串的 内容 的长度,而省略了 C 用来终止字符串的尾部 ASCII NUL 字符。任何时候你看到:malloc(strlen(foo));它几乎肯定是一个 bug。总是写成 malloc(strlen(foo) + 1);。让那个 + 1 明显起来,因为任何时候你 看到那个 + 1,你可能已经发现了一个 bug。

strcpy(vect[q], var);

由于vect[q]没有足够的内存分配,这一行代码将ASCII NUL字符覆盖了一个不相关的字节。

至于释放内存的free(3),大多数程序员发现在同一时间编写malloc(3)free(3)调用非常有帮助,使用相同的源代码检查,以便可以轻松地添加或删除功能。是否有一个简单且相应的“拆卸”函数与您刚刚编写的函数相对应?..._init()函数通常具有..._final()函数,..._open()函数通常具有..._close()函数等等。这些成对的函数非常适合内存管理。


尝试过了,现在程序崩溃了。如果我移除那个“free”的东西,它就不会崩溃。 - FinalDestiny

1

这段代码可以在valgrind下编译和运行,没有任何问题。

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

static void svnViewStatus(void)
{
    FILE *file;
    int i, j, k, q=0, ok;
    char mystring[100000], *vect[100000], *var, last[12], first[12];
    file = fopen("db_svnViewStatus.txt", "r");
    if (file == NULL)
    {
        printf("error");
        return;
    }

    for (i = 1; fgets(mystring, sizeof(mystring), file) != 0; i++)
    {
        if (i == 1) /* Skip heading */
            continue;
        char *space = mystring;
        for (j = 1; (var = strtok(space, ";")) != NULL; space = NULL, j++)
        {
            if (j == 3)
            {
                if (i == 2)
                {
                    strcpy(first, var);
                }
                strcpy(last, var);
            }
            if (j == 2)
            {
                if (q != 0)
                {
                    ok = 1;
                    for (k = 0; k<q; k += 2)
                    {
                        if (strcmp(vect[k], var) == 0)
                        {
                            (*vect[k+1])++;
                            ok=0;
                        }
                    }
                    if (ok == 1)
                    {
                        vect[q] = malloc(strlen(var)+1);
                        strcpy(vect[q], var);
                        vect[q+1] = malloc(sizeof(int));
                        *vect[q+1] = 1;
                        q += 2;
                    }
                }
                else
                {
                    vect[q] = malloc(strlen(var)+1);
                    strcpy(vect[q], var);
                    vect[q+1] = malloc(sizeof(int));
                    *vect[q+1] = 1;
                    q += 2;
                }
            }
        }
    }

    fclose(file);
    printf("nr: %d \n", i-2);
    printf("first: %s \n", first);
    printf("last: %s \n", last);
    for (i=0; i<q; i = i+2)
    {
        printf("User %s: %d \n", vect[i], *vect[i+1]);
    }
    for (i=0; i<q; i=i+1)
    {
        free(vect[i]);
    }
}

int main(void)
{
    svnViewStatus();
    return 0;
}

它比原来少了几个缩进级别。代码超出屏幕右侧是问题的指示,最常见的情况是这样。如评论所述,主要问题在于旨在通过指针递增整数的表达式实际上递增了指针而不是整数。有一对for循环替换了while循环,以便循环控制都在循环顶部。通常这种方式更容易控制循环。

我仍然对代码不满意。您正在分配int并将char *视为int *,这意味着在64位机器上,您具有指向4字节数据的8字节指针(和两个malloc()调用)。如果使用结构体,例如:

struct data
{
    char *string;
    int   count;
};

struct data vect[5000];

有了这个,你只需要将字符串分配(复制)到结构的下一个元素中。这样更紧凑,也更不容易出错。(你可以写 vect[i].count++;,它会按照你想要的方式执行,没有任何麻烦。)而且你不需要再去处理 q += 2;(或者 q = q + 2;)。

使用结构体的代码更好。它还检查内存分配,并确保复制到 firstlast 中的名称不会溢出(并且以空字符结尾)。它仍然没有对 vect 数组进行边界检查,以确保它不会覆盖末尾。如果在读取文件时出现错误,则会泄漏内存;清理需要一些小心(和一个函数来完成)。

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

struct data
{
    char *string;
    int   count;
};

static void svnViewStatus(void)
{
    FILE *file;
    int i, q = 0;
    char mystring[100000], last[12], first[12];
    struct data vect[50000];
    file = fopen("db_svnViewStatus.txt", "r");
    if (file == NULL)
    {
        printf("file open error\n");
        return;
    }

    for (i = 1; fgets(mystring, sizeof(mystring), file) != 0; i++)
    {
        if (i == 1) /* Skip heading */
            continue;
        char *space = mystring;
        char *var;
        for (int j = 1; (var = strtok(space, ";")) != NULL; space = NULL, j++)
        {
            if (j == 3)
            {
                if (i == 2)
                {
                    strncpy(first, var, sizeof(first)-1);
                    first[sizeof(first)-1] = '\0';
                }
                strncpy(last, var, sizeof(last)-1);
                last[sizeof(last)-1] = '\0';
            }
            if (j == 2)
            {
                int found = 0;
                for (int k = 0; k < q; k++)
                {
                    if (strcmp(vect[k].string, var) == 0)
                    {
                        vect[k].count++;
                        found = 1;
                    }
                }
                if (found == 0)
                {
                    vect[q].string = strdup(var);
                    if (vect[q].string == 0)
                    {
                        printf("Memory allocation error\n");
                        return;
                    }
                    vect[q].count = 1;
                    q++;
                }
            }
        }
    }

    fclose(file);

    printf("nr: %d\n", i-1);
    printf("first: %s\n", first);
    printf("last: %s\n", last);
    for (i = 0; i < q; i++)
    {
        printf("User %s: %d\n", vect[i].string, vect[i].count);
    }

    for (i = 0; i < q; i++)
    {
        free(vect[i].string);
    }
}

int main(void)
{
    svnViewStatus();
    return 0;
}

0
回答你的最后一个问题:
另外,我在分配内存后应该使用free吗?为什么?
是的,你应该使用free(); 如果不使用free(),你就有可能出现内存泄漏的问题。
你应该在函数的末尾,在return语句之前调用free()。
但是,在您的情况下,调用函数的函数main在svnViewStatus()函数完成并将控制返回到main时也可以调用free()。这是因为可以使用不同的指针变量来使用free(); 你只需要确保新的指针存储相同的地址。我的意思是malloc分配的内存块的第一个字节的地址。
正如dasblinkenlight所提到的,你需要为终止零分配内存。
希望这有所帮助。

0
首先,在这个表达式语句中:
*vect[k+1]++;

你有一个运算符优先级问题。你可能想要做这个:

(*vect[k+1])++;

那么在这个函数调用中:

printf("User %s: %d \n", *vect[i], *vect[i+1]);

vect[i]的类型是char指针。要打印字符串,您不需要取消引用它,而可能想要执行以下操作:

printf("User %s: %d \n", vect[i], *vect[i+1]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接