0

这真的很奇怪......我无法调试它(尝试了大约两个小时,调试器在一段时间后开始失控......)。无论如何,我正在尝试做一些非常简单的事情:

Free an array of strings. 数组形式为:

char **myStrings. 数组元素初始化为:

myString[index] = malloc(strlen(word));
myString[index] = word;

我正在调用这样的函数:

free_memory(myStrings, size);其中 size 是数组的长度(我知道这不是问题,我对它进行了广泛的测试,除了这个函数之外的所有东西都可以工作)。

free_memory看起来像这样:

void free_memory(char **list, int size) {

    for (int i = 0; i < size; i ++) {
        free(list[i]);
    }

    free(list);
}

现在奇怪的部分来了。if (size> strlen(list[i]))然后程序崩溃。例如,假设我有一个看起来像这样的字符串列表:

myStrings[0] = "Some";
myStrings[1] = "random";
myStrings[2] = "strings";

因此这个数组的长度是3

如果我将它传递给我的free_memory函数strlen(myStrings[0]) > 34 > 3),程序就会崩溃。

但是,如果我改为myStrings[0]改为"So",则strlen(myStrings[0]) < 3( 2 < 3 ) 并且程序不会崩溃

所以在我看来,free(list[i])实际上char[]是在通过那个位置并试图释放每个角色,我认为这未定义的行为

我这么说的唯一原因是因为我可以玩弄第一个元素的大小,myStrings并在我喜欢的时候让程序崩溃,所以我假设这是问题区域。

注意:我确实尝试通过单步执行调用的函数来调试它free_memory,注意到任何奇怪的值等,但是当我踏入free_memory函数的那一刻,调试器崩溃了,所以我不确定发生了什么。在我进入函数之前,没有什么不寻常的,然后世界就爆炸了。

另一个注意事项:我还在此处发布了该程序源代码的缩短版本(不太长;Pastebin)。我正在使用 c99 标志在 MinGW 上编译。

PS-我只是想到了这一点。我确实传递numUniqueWords给了 free 函数,我知道这实际上并没有释放我分配的整个内存。我已经把它称为两种方式,这不是问题。我把它按照我的做法留下了,因为这是我首先让它工作后调用它的方式,我需要修改我在那个函数中的一些逻辑。

来源,根据要求(现场)

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

int getNumUniqueWords(char text[], int size);

int main(int argc, char* argv[]) {

        setvbuf(stdout, NULL, 4, _IONBF); // For Eclipse... stupid bug. --> does NOT affect the program, just the output to console!

        int nbr_words;

        char text[] = "Some - \"text, a stdin\". We'll have! also repeat? We'll also have a repeat!";
        int length = sizeof(text);
        nbr_words = getNumUniqueWords(text, length);

        return 0;
}

void free_memory(char **list, int size) {

        for (int i = 0; i < size; i ++) {
                // You can see that printing the values is fine, as long as free is not called.
                // When free is called, the program will crash if (size > strlen(list[i]))
                //printf("Wanna free value %d w/len of %d: %s\n", i, strlen(list[i]), list[i]);
                free(list[i]);
        }
        free(list);
}

int getNumUniqueWords(char text[], int length) {
        int numTotalWords = 0;
        char *word;

        printf("Length: %d characters\n", length);

        char totalWords[length];
        strcpy(totalWords, text);

        word = strtok(totalWords, " ,.-!?()\"0123456789");

        while (word != NULL) {
                numTotalWords ++;
                printf("%s\n", word);
                word = strtok(NULL, " ,.-!?()\"0123456789");
        }

        printf("Looks like we counted %d total words\n\n", numTotalWords);

        char *uniqueWords[numTotalWords];
        char *tempWord;
        int wordAlreadyExists = 0;
        int numUniqueWords = 0;

        char totalWordsCopy[length];
        strcpy(totalWordsCopy, text);

        for (int i = 0; i < numTotalWords; i++) {
                uniqueWords[i] = NULL;
        }

        // Tokenize until all the text is consumed.
        word = strtok(totalWordsCopy, " ,.-!?()\"0123456789");
        while (word != NULL) {

                // Look through the word list for the current token.
                for (int j = 0; j < numTotalWords; j ++) {
                        // Just for clarity, no real meaning.
                        tempWord = uniqueWords[j];

                        // The word list is either empty or the current token is not in the list.
                        if (tempWord == NULL) {
                                break;
                        }

                        //printf("Comparing (%s) with (%s)\n", tempWord, word);

                        // If the current token is the same as the current element in the word list, mark and break
                        if (strcmp(tempWord, word) == 0) {
                                printf("\nDuplicate: (%s)\n\n", word);
                                wordAlreadyExists = 1;
                                break;
                        }
                }

                // Word does not exist, add it to the array.
                if (!wordAlreadyExists) {
                        uniqueWords[numUniqueWords] = malloc(strlen(word));
                        uniqueWords[numUniqueWords] = word;
                        numUniqueWords ++;
                        printf("Unique: %s\n", word);
                }

                // Reset flags and continue.
                wordAlreadyExists = 0;
                word = strtok(NULL, " ,.-!?()\"0123456789");
        }

        // Print out the array just for funsies - make sure it's working properly.
        for (int x = 0; x <numUniqueWords; x++) {
                printf("Unique list %d: %s\n", x, uniqueWords[x]);
        }

        printf("\nNumber of unique words: %d\n\n", numUniqueWords);

        // Right below is where things start to suck.
        free_memory(uniqueWords, numUniqueWords);

        return numUniqueWords;
}
4

5 回答 5

10

你已经得到了这个问题的答案,所以让我来回答一个不同的问题:

我有多个容易犯的错误——分配错误大小的缓冲区并释放非 malloc 的内存。我调试了几个小时,却一无所获。我怎样才能更有效地度过这段时间?

您本可以花费这些时间编写自己的内存分配器,以自动发现错误。

当我编写大量 C 和 C++ 代码时,我为我的程序创建了帮助方法,这些方法将所有 malloc 和释放转换为不仅仅是分配内存的调用。(请注意,像 strdup 这样的方法是伪装的 malloc。)如果用户要求,比如说,32 个字节,那么我的辅助方法将添加 24 并实际分配 56 个字节。(这是在具有 4 字节整数和指针的系统上。)我保留了一个静态计数器和一个双向链表的静态头尾。然后我将填写我分配的内存,如下所示:

  • 字节 0-3:计数器
  • 字节 4-7:双向链表的 prev 指针
  • 字节 8-11:双向链表的下一个指针
  • 字节 12-15:实际传递给分配器的大小
  • 字节 16-19:01 23 45 67
  • 字节 20-51:33 33 33 33 33 33 ...
  • 字节 52-55:89 AB CD EF

并返回一个指向字节 20 的指针。

自由代码将传入的指针减去四,并验证字节 16-19 仍然是 01 23 45 67。如果它们不是,那么要么你正在释放一个你没有用这个分配器分配的块,要么你已经不知何故写指针之前。无论哪种方式,它都会断言。

如果该检查成功,那么它将再返回四次并读取大小。现在我们知道块的末尾在哪里,我们可以验证字节 52 到 55 仍然是 89 AB CD EF。如果不是,那么您正在某处写在一个块的末尾。再次,断言。

现在我们知道该块没有损坏,我们将其从链表中删除,将块的所有内存设置为 CC CC CC CC ... 并释放该块。我们使用 CC 是因为那是 x86 上的“闯入调试器”指令。如果我们以某种方式最终使指令指针指向这样的块,那么如果它中断就好了!

如果有问题,那么您也知道它是哪个分配,因为您在块中有分配计数。

现在我们有一个系统可以为您找到错误。在您的产品的发布版本中,只需将其关闭,以便您的分配器正常调用 malloc。

此外,您可以使用此系统查找其他错误。例如,如果您认为某处发生了内存泄漏,您所要做的就是查看链表;您拥有所有未完成分配的完整列表,并且可以找出哪些分配是不必要的。如果您认为您为给定块分配了太多内存,那么您可以让您的空闲代码检查该块中是否有很多 33 即将被释放;这表明您分配的块太大了。等等。

最后:这只是一个起点。当我专业地使用这个调试分配器时,我对其进行了扩展,使其成为线程安全的,这样它就可以告诉我正在执行分配的是哪种分配器(malloc、strdup、new、IMalloc 等),是否存在不匹配alloc 和 free 函数,包含分配的源文件,分配时调用堆栈是什么,平均,最小和最大块大小是什么,哪些子系统负责哪些内存使用......

C 要求你管理自己的内存;这肯定有它的优点和缺点。我的观点是利大于弊;我更喜欢使用自动存储语言。但是必须管理自己的存储的好处是,您可以自由地构建满足您需求的存储管理系统,其中包括您的调试需求。如果您必须使用一种需要您管理存储的语言,请充分利用该功能并构建一个非常强大的子系统,您可以使用它来解决专业级问题。

于 2013-10-21T15:51:52.320 回答
4

问题不在于您如何释放,而在于您如何创建数组。考虑一下:

uniqueWords[numUniqueWords] = malloc(strlen(word));
uniqueWords[numUniqueWords] = word;

...

word = strtok(NULL, " ,.-!?()\"0123456789");

这里有几个问题:

word = strtok(): strtok 返回的东西不是你可以释放的,因为它没有被 malloc'ed。即它不是一个副本,它只是指向底层大字符串中的某个地方(你首先调用 strtok 的东西)。

uniqueWords[numUniqueWords] = word: 这不是副本;它只是分配指针。之前的指针(你 malloc 的)被覆盖。

malloc(strlen(word)): 这分配的内存太少,应该是 strlen(word)+1

怎么修:

选项A:正确复制

// no malloc
uniqueWords[numUniqueWords] = strdup(word); // what strdup returns can be free'd

选项 B:正确复制,稍微详细一点

uniqueWords[numUniqueWords] = malloc(strlen(word)+1);
strcpy(uniqueWords[numUniqueWords], word); // use the malloc'ed memory to copy to

选项 C:不复制,不免费

// no malloc
uniqueWords[numUniqueWords] = word; // not a copy, this still points to the big string
// don't free this, ie don't free(list[i]) in free_memory

编辑正如其他人指出的那样,这也是有问题的:

    char *uniqueWords[numTotalWords];

我相信这是一个 GNU99 扩展(甚至不是 C99),实际上你不能(不应该)释放它。试试char **uniqueWords = (char**)malloc(sizeof(char*) * numTotalWords)。同样,问题不是 free() 而是您分配的方式。您在 free 的正确轨道上,只需将每个 free 与 malloc 匹配,或者与说它等同于 malloc 的东西(如 strdup)匹配。

于 2013-10-21T05:03:32.090 回答
4

您正在使用此代码来尝试分配内存:

uniqueWords[numUniqueWords] = malloc(strlen(word));
uniqueWords[numUniqueWords] = word;
numUniqueWords++;

这在很多层面上都是错误的。

  1. 您需要分配strlen(word)+1内存字节。
  2. 您需要strcpy()在分配的内存上分配字符串;目前,您只需将分配的内存扔掉即可。

您的数组uniqueWords本身未分配,并且word您存储的值来自已被strtok().

就目前而言,您无法释放任何内存,因为您已经丢失了指向已分配内存的指针,并且您尝试释放的内存实际上从未由malloc()et al 分配。

而且您也应该在检查内存分配时出错。考虑使用strdup()复制字符串。

于 2013-10-21T05:08:43.140 回答
0

您正在尝试 free char *uniqueWords[numTotalWords];,这在 C 中是不允许的。

由于uniqueWords是在堆栈上分配的,因此您无法调用free堆栈内存。

只需删除最后一个free调用,如下所示:

void free_memory(char **list, int size) {

    for (int i = 0; i < size; i ++) {
        free(list[i]);
    }
}
于 2013-10-21T04:59:53.453 回答
0

分配和释放 char 数组的正确方法。

char **foo = (char **) malloc(row* sizeof(char *));

*foo = malloc(row * col * sizeof(char));

for (int i = 1; i < row; i++) {
  foo[i] = *foo + i*col;
}
free(*foo);
free(foo);

请注意,您不需要遍历数组的每个元素来释放内存。数组是连续的,因此请在数组名称上调用 free。

于 2013-10-21T05:05:44.967 回答