C free(): 无效指针

36

我正在自学C语言。我的目标是编写一个C函数,遍历查询字符串并在"&"和"="处进行分割。但是我遇到了Valgrind报错的困难。

==5411== Invalid free() / delete / delete[] / realloc()
==5411==    at 0x402AC38: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==5411==    by 0x804857C: main (leak.c:28)
==5411==  Address 0x420a02a is 2 bytes inside a block of size 8 free'd
==5411==    at 0x402AC38: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==5411==    by 0x804857C: main (leak.c:28)
==5411== 
==5411== 
==5411== HEAP SUMMARY:
==5411==     in use at exit: 0 bytes in 0 blocks
==5411==   total heap usage: 1 allocs, 2 frees, 8 bytes allocated
==5411== 
==5411== All heap blocks were freed -- no leaks are possible
==5411== 
==5411== For counts of detected and suppressed errors, rerun with: -v
==5411== ERROR SUMMARY: 20 errors from 9 contexts (suppressed: 0 from 0)

同时还有回溯信息:

*** Error in `./leak': free(): invalid pointer: 0x08c1d00a ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x767c2)[0xb75f17c2]
/lib/i386-linux-gnu/libc.so.6(+0x77510)[0xb75f2510]
./leak[0x804857d]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0xb7594905]
./leak[0x8048421]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:05 262764     /home/danny/dev/c-qs-parser/leak
08049000-0804a000 r--p 00000000 08:05 262764     /home/danny/dev/c-qs-parser/leak
0804a000-0804b000 rw-p 00001000 08:05 262764     /home/danny/dev/c-qs-parser/leak
08c1d000-08c3e000 rw-p 00000000 00:00 0          [heap]
b757a000-b757b000 rw-p 00000000 00:00 0 
b757b000-b7729000 r-xp 00000000 08:05 1312132    /lib/i386-linux-gnu/libc-2.17.so
b7729000-b772b000 r--p 001ae000 08:05 1312132    /lib/i386-linux-gnu/libc-2.17.so
b772b000-b772c000 rw-p 001b0000 08:05 1312132    /lib/i386-linux-gnu/libc-2.17.so
b772c000-b772f000 rw-p 00000000 00:00 0 
b772f000-b774a000 r-xp 00000000 08:05 1312589    /lib/i386-linux-gnu/libgcc_s.so.1
b774a000-b774b000 r--p 0001a000 08:05 1312589    /lib/i386-linux-gnu/libgcc_s.so.1
b774b000-b774c000 rw-p 0001b000 08:05 1312589    /lib/i386-linux-gnu/libgcc_s.so.1
b774c000-b7750000 rw-p 00000000 00:00 0 
b7750000-b7751000 r-xp 00000000 00:00 0          [vdso]
b7751000-b7771000 r-xp 00000000 08:05 1312116    /lib/i386-linux-gnu/ld-2.17.so
b7771000-b7772000 r--p 0001f000 08:05 1312116    /lib/i386-linux-gnu/ld-2.17.so
b7772000-b7773000 rw-p 00020000 08:05 1312116    /lib/i386-linux-gnu/ld-2.17.so
bfe93000-bfeb4000 rw-p 00000000 00:00 0          [stack]
Aborted (core dumped)

最后,这里是代码:

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

int main() {

    //char p[] = "t=quote&k=id&v=10";
    char p[] = "t=quote";

    char* token;
    char* tk; 
    char* s;
    unsigned short int found;

    s = strdup(p);

    if (s != NULL) {
        while ((token = strsep(&s, "&")) != NULL) {

            found = 0;

            printf("TOKEN: %s\n\n", token);

            while ((tk = strsep(&token, "=")) != NULL) {

                printf("TK: %s\n\n", tk);

                free(tk);
            }   

            free(token);
        }   
    }   

    free(s);

    return 0;
}

谢谢


1
感谢您的回复。当我去掉free(token)和free(tk)时,Valgrind报告说我泄漏了8个字节。当我把它们放回去时,它报告说我不再泄漏,但程序无法运行。 - Dan
3个回答

65

您试图释放的是指向不可被释放内存地址的非指针。仅仅因为某个东西是一个地址并不意味着您需要或应该释放它。

有两种主要类型的内存,您似乎在混淆-栈内存和堆内存。

  • 栈内存存在于函数的生存期中。它是用于那些不应该增长太大的临时空间。当您调用函数 main 时,它会为您声明的变量(ptoken等)设置一些内存。

  • 堆内存从您使用 malloc 到您使用 free 。您可以使用比栈内存多得多的堆内存。您还需要跟踪它-它不像栈内存那样容易!

您有几个错误:

  • 您正在尝试释放不是堆内存的内存。请勿这样做。

  • 您正在尝试释放内存块的内部。实际上,在您分配了一个内存块时,您只能从malloc返回的指针处释放它。也就是说,只能从块的开头释放它。您无法从内部释放块的一部分。

对于您这里的代码片段,您可能想找到一种方法将相关内存的一部分复制到其他地方...比如您预留的另一个内存块。或者,如果您愿意,可以修改原始字符串(提示:字符值0是空终止符,并告诉像printf这样的函数停止读取字符串)。

编辑:malloc函数确实分配堆内存*。

"9.9.1 malloc和free函数

C标准库提供了一个名为malloc包的显式分配器。程序通过调用malloc函数从堆中分配块."

计算机系统:程序员的视角,第二版,Bryant & O'Hallaron,2011年

编辑2:*实际上,C标准没有规定堆或栈的任何内容。但是,对于在相关台式机/笔记本电脑上学习的任何人来说,这种区别可能是不必要的和令人困惑的,特别是如果您正在学习有关程序如何存储和执行的知识。当您发现自己像H2CO3一样在处理AVR微控制器时,一定值得注意所有的差异,从我自己在嵌入式系统方面的经验来看,这些差异远远超出了内存分配。


2
你正在尝试释放非堆内存的内存。 - 未指定malloc()(等)是否返回指向“堆内存”,“栈内存”或其他内容的指针。重要的是它不是由标准分配器函数分配的。 - user529758
@H2CO3 不必要的复制?什么是必要的?这只是一个练习。另外,我查了一下malloc,它似乎确实分配堆内存。 - GraphicsMuncher
2
不必要的拷貝是多餘的,並且術語很難理解。在大多數系統上,malloc()確實會返回“堆內存”,但標準沒有提到這一點,因此可能存在沒有“堆”的系統。另一方面,在這些系統上,我們可以擴展“堆”的定義,將其視為malloc()從其中取得內存的內存區域,這樣一切都好了。 - glglgl
1
这个答案是误导性的。@H2CO3和qwrrty的是正确的。 - Jens Gustedt
我已经更新了我的答案。我认为这种区别在实践中是一个深奥的漏洞(特别是对于在普通计算机上学习的初学者),尽管我不能反驳它,更何况H2CO3的例子。 - GraphicsMuncher
显示剩余3条评论

11
你从哪里得到的想法,认为你需要使用free(token)free(tk)?其实不需要。 strsep() 不会分配内存,它只返回原始字符串中的指针。当然,这些指针并不是由malloc()(或类似函数)分配的,所以释放它们的行为是未定义的。只有在处理完整个字符串后才需要free(s)
另外请注意,在你的示例中根本不需要动态内存分配。你可以通过简单地编写char *s = p;来避免使用strdup()free()

好的,我去掉了strdup调用,Valgrind报告没有更多的内存泄漏。非常感谢。 - Dan
@user964491(欢迎,但拜托下次阅读文档!) - user529758
@user964491 但请注意,你接受的答案是 错误的(可能从我的评论中可以看出来)... - user529758
1
@user964491 不,唯一重要的是你接受了一个错误的答案,现在每个看到它的访问者(并且不足以区分好坏)都会说“嘿,太棒了,这就是我应该做的”,但实际上他们不应该这样做,因为他们会习惯于根本错误的做法和同样错误的术语。所以你最终会对社区做出非常恶劣的事情。(但这是你的决定,我不能说服其他人以这种或那种方式做他们的事情,所以你自己决定吧...) - user529758
1
如果这三个答案都留在这个帖子上,我认为他们会明白的。 - Dan
显示剩余4条评论

3

您不能在从 strsep 返回的指针上调用 free 。这些不是单独分配的字符串,而只是指向您已经分配的字符串 s 的指针。当您完全使用 s 后,应释放它,但不必对 strsep 的返回值进行此操作。


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