移动指针后C中的内存泄漏问题(究竟是什么解除分配?)

时间:2009-08-12 15:37:26

标签: c memory-management memory-leaks malloc

我意识到下面的代码示例是你永远不应该做的事情。我的问题只是一个有趣的问题。如果你分配一块内存,然后移动指针(一个禁忌),当你释放内存时,释放的块的大小是多少,内存在哪里?这是人为的代码片段:

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

int main(void) {
    char* s = malloc(1024);
    strcpy(s, "Some string");
    // Advance the pointer...
    s += 5;
    // Prints "string"
    printf("%s\n", s);
    /*
     * What exactly are the beginning and end points of the memory 
     * block now being deallocated?
     */
    free(s);
    return 0;
}

这就是我认为我发生的事情。被释放的内存块以保存字符串“s”的字节开头。持有“Some”的5个字节现在丢失了。

我想知道的是:在内存中的位置紧跟在原始1024字节的末尾之后的5个字节是否已经解除分配,或者它们是否只是单独存在?

任何人都知道编译器的作用是什么?这是不确定的?

感谢。

11 个答案:

答案 0 :(得分:18)

您无法将未从malloccallocrealloc获取的指针传递给freeNULL除外)。

C FAQ中的

Question 7.19与您的问题相关。

解释consequences of invoking undefined behavior here

答案 1 :(得分:7)

这是标准中未定义的行为,因此您不能依赖任何内容。

请记住,块是人为分隔的内存区域,并不是自动的 出现。有必要跟踪块,以释放所有必要的东西,仅此而已。没有可能的终止,比如C字符串,因为没有值或值的组合可以保证不在块内。

最后我看,有两种基本的实施方法。

一种方法是保留已分配块的单独记录以及分配的地址。 free()函数查找块以查看要释放的内容。在这种情况下,它可能根本找不到它,并且很可能什么都不做。内存泄漏。但是,没有任何保证。

一种方法是在分配地址之前将块信息保存在内存的一部分中。在这种情况下,free()使用块的一部分作为块描述符,并且根据存储的内容(可能是任何内容),它将释放一些东西。它可能是一个太小的区域,也可能是一个太大的区域。堆腐败的可能性很大。

所以,我希望内存泄漏(没有任何内容被释放)或堆损坏(太多被标记为空闲,然后重新分配)。

答案 2 :(得分:5)

是的,这是未定义的行为。你基本上是在释放一个没有malloc的指针。

答案 3 :(得分:4)

您无法将未从malloc(或callocrealloc ...)获得的指针传递给free。这包括您从malloc获得的块的偏移量。违反此规则可能会导致发生任何。通常情况下,这最终可能是在最糟糕的时刻出现的最糟糕的可能性。

进一步说明,如果你想截断这个块,有一个合法的方法可以做到这一点:

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

int main() {
    char *new_s;
    char *s = malloc(1024);
    strcpy(s, "Some string");

    new_s = realloc(s, 5);
    if (!new_s) {
        printf("Out of memory! How did this happen when we were freeing memory? What a cruel world!\n");
        abort();
    }
    s = new_s;

    s[4] = 0; // put the null terminator back on
    printf("%s\n", s); // prints Some

    free(s);
    return 0;
}

realloc可用于放大和缩小内存块,但可能(或可能不)移动内存来执行此操作。

答案 4 :(得分:2)

它不是编译器,它是标准库。行为未定义。图书馆知道它已将原始s分配给您。 s+5未分配给库已知的任何内存块,即使它恰好位于已知块内。所以,它不会起作用。

答案 5 :(得分:2)

  

我想知道的是:在内存中的位置紧跟在原始1024字节的末尾之后的5个字节是否已经解除分配,或者它们是否只是单独存在?

两者。结果是未定义的,因此编译器可以自由地执行其中任何一个,或者他们真正喜欢的任何其他内容。当然(对于特定平台和编译器的所有“未定义行为”的情况),都有一个特定的答案,但任何依赖这种行为的代码都是一个坏主意。

答案 6 :(得分:2)

对未被malloc或其兄弟分配的ptr调用free()是未定义的。

malloc的大多数实现在ptr返回之前立即分配一个小的(通常是4byte)头区域。这意味着当你分配1024个字节时,malloc实际上保留了1028个字节。当调用free(ptr)时,如果ptr不为0,它将检查ptr-sizeof(header)中的数据。一些分配器实现了一个健全性检查,以确保它是一个有效的头,并且可能检测到一个坏的ptr,并断言或退出。如果没有进行健全性检查,或者错误地通过,则自由例程将对标题中出现的任何数据起作用。

答案 7 :(得分:1)

添加更正式的答案:我将这个问题的机制与在图书馆(malloc)中拿书的人进行比较,然后与封面一起撕下几十页(推进指针),然后尝试归还(免费)。

您可能会找到一本图书管理员(malloc /免费图书馆实施),这本书可以拿回这本书,但在很多情况下,我希望您会因疏忽处理而支付罚金。

在C99的选秀中(我没有最终的C99在我面前),在这个主题上有一些话要说:

  

free函数会导致ptr指向的空格被释放,   也就是说,可供进一步分配。如果ptr是空指针,则不执行任何操作   发生。否则,如果参数与先前返回的指针不匹配   通过callocmallocrealloc函数,或者空格如何   通过调用freerealloc取消分配,行为未定义。

根据我的经验,双重免费或没有通过malloc返回的“指针”将导致内存损坏和/或崩溃,具体取决于您的malloc实施。围栏两侧的安全人员不再使用此行为,至少在广泛使用的Doug Lea的malloc包的早期版本中various interesting things

答案 8 :(得分:0)

库实现可能会在返回给你的指针之前放置一些数据结构。然后在free()中,它递减指针以获取数据结构,告诉它如何将内存放回到空闲池中。因此,字符串“Some”开头的5个字节被解释为struct算法使用的malloc()的结尾。也许是32位值的结束,如分配的内存大小,或链表中的链接。这取决于实施。无论细节如何,它都会让你的程序崩溃。正如思南指出的那样,如果你很幸运!

答案 9 :(得分:0)

让我们在这里变得聪明...... free()不是黑洞。至少,你有CRT源代码。除此之外,您还需要内核源代码。

当然,行为未定义,因为由CRT / OS来决定做什么。但这并不妨碍你找出你的平台实际上做了什么。

快速浏览Windows CRT会显示free()使用CRT特定堆直接HeapFree()。 Beoyond你进入RtlHeapFree()然后进入系统空间(NTOSKRN.EXE)与内存管理器Mm*()

在所有这些代码路径中都有一些检查。但是对内存执行不同的操作会导致不同的代码路径。因此,未定义的真正定义。

快速浏览一下,我可以看到分配的内存块最后有一个标记。释放存储器时,每个字节都用不同的字节写入。运行时可以检查是否覆盖了块标记的结尾,如果是,则引发异常。

在你的内存中释放几个字节(或覆盖你分配的大小)的情况下,这是一种可行性。当然你可以欺骗它并在正确的位置自己编写块标记的结尾。这将使您通过CRT检查,但随着代码路径的进一步发展,会出现更多未定义的行为。可能会发生以下三种情况:1)绝对无害,2)CRT堆内存损坏,或3)任何内存管理函数抛出异常。

答案 10 :(得分:-2)

简短版本:这是未定义的行为。

长版本:我检查了CWE site并发现,虽然这是一个坏主意,但没有人似乎有一个坚实的答案。可能是因为它未定义。

我的猜测是,大多数实现,假设它们不会崩溃,将释放1019个字节(在您的示例中),或者释放1024并在后五个中获得双倍或类似的。从理论上说现在,它取决于malloc例程的内部存储表是否包含地址和长度,或者起始地址和结束地址。

无论如何,这显然不是一个好主意。 : - )