C中自由的奇怪(未定义?)行为

时间:2013-10-21 04:47:44

标签: c arrays free undefined-behavior

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

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]) > 3 4&gt; 3 ),程序崩溃。

但是,如果我将myStrings[0]更改为"So",那么strlen(myStrings[0]) < 3 2 <3 )并且程序不< / em>崩溃。

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

我说这个的唯一原因是因为我可以使用myStrings的第一个元素的大小,并且只要我感觉它就会使程序崩溃,所以我假设这是问题区域

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

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

PS - 我只想到了这一点。我确实将numUniqueWords传递给了自由函数,我知道这实际上并没有释放我分配的整个内存。我已经两种方式称呼它,这不是问题。我离开了它是怎么做的,因为这是我在第一次开始工作后调用它的方式,我需要在该函数中修改我的一些逻辑。

来源,根据要求(现场)

#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;
}

5 个答案:

答案 0 :(得分:10)

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

  

我有多个容易犯的错误 - 分配错误大小的缓冲区并释放非malloc内存。我调试了几个小时,无处可去。我怎么能更有效地度过那段时间?

您可能花了几个小时编写自己的内存分配器,这些内存分配器会自动发现错误。

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

  • 字节0-3:计数器
  • 字节4-7:双向链表的上一个指针
  • 字节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要求你管理自己的记忆;这绝对有其优点和缺点。我的观点是,利弊超过了职业选手;我更喜欢使用自动存储语言。但是,必须管理自己的存储的好处是您可以自由构建满足您需求的存储管理系统,其中包括您的调试需求。如果您必须使用需要您管理存储的语言,请使用该功能,并构建一个非常强大的子系统,以便解决专业级问题。

答案 1 :(得分:4)

问题不是你如何解放,而是你如何创建阵列。考虑一下:

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

...

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

这里有几个问题:

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

uniqueWords[numUniqueWords] = word:这不是副本;它只是指定指针。

。之前存在的指针(你被malloc'ed)覆盖。

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()而是你分配的方式。你是免费的,只需要将每个免费的东西与malloc相匹配,或者说它相当于malloc(如strdup)。

答案 2 :(得分:4)

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

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

这在许多层面都是错误的。

  1. 您需要分配strlen(word)+1个字节的内存。
  2. 你需要在分配的内存上strcpy()字符串;此刻,你只需抛弃分配的内存。
  3. 您的数组uniqueWords本身未分配,您存储的word值来自已被strtok()删除的原始字符串。

    就目前而言,你不能释放任何内存,因为你已经丢失了指向已分配的内存的指针,而你试图释放的内存实际上从未由malloc()等分配。

    你也应该错误地检查内存分配。考虑使用strdup()复制字符串。

答案 3 :(得分:0)

您正试图释放char *uniqueWords[numTotalWords];,这是C中不允许的。

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

只需删除最后一次free电话,如下所示:

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

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

答案 4 :(得分: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);

请注意,您无需查看每个&amp;数组的每个元素用于释放内存。数组是连续的,因此可以在数组名称上自由调用。