valgrind - 地址 ---- 在分配了大小为8的块后为0字节

39

首先,我知道有类似的问题被问到过。但是,我想提出一个更通俗易懂的问题,关于真正基本的C数据类型。那么问题来了。

main.c中,我调用了一个函数来填充这些字符串:

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

    char *host = NULL ;
    char *database ;
    char *collection_name;
    char *filename = ""; 
    char *fields = NULL;
    char *query = NULL;
    ...

    get_options(argc, argv, &host, &database, &collection_name, &filename, 
                &fields, &query, &aggregation);

get_options 函数内:
if (*filename == NULL ) {
   *filename = (char*)realloc(*filename, strlen(*collection_name)*sizeof(char)+4);
    strcpy(*filename, *collection_name);
    strcat(*filename, ".tde");  # line 69 
}

我的程序运行良好,但Valgrind告诉我我做错了:

==8608== Memcheck, a memory error detector
==8608== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==8608== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==8608== Command: ./coll2tde -h localhost -d test -c test
==8608== 
==8608== Invalid write of size 1
==8608==    at 0x403BE2: get_options (coll2tde.c:69)
==8608==    by 0x402213: main (coll2tde.c:92)
==8608==  Address 0xa2edd18 is 0 bytes after a block of size 8 alloc'd
==8608==    at 0x4C28BED: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8608==    by 0x4C28D6F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8608==    by 0x403BBC: get_options (coll2tde.c:67)
==8608==    by 0x402213: main (coll2tde.c:92)

你能解释一下错误Address 0xa2edd18 is 0 bytes after a block of size 8 alloc'd吗?我该如何解决这个问题?

2
说实话,我觉得这个错误信息有点令人困惑,因为我误解了“after”的含义。我以为它的意思是“在分配块之后的时间”,但实际上它的意思是“在分配块之后的内存中”。换句话说:地址0x...出现在大小为8的已分配块的紧随其后(0字节) - mwfearnley
3个回答

57

strcpy会添加一个空终止字符'\0'。你忘记为它分配空间了:

strcpy会自动在复制的字符串末尾添加一个表示结束的空字符'\0',但你忘记了为它预留空间:

*filename = (char*)realloc(*filename, strlen(*collection_name)*sizeof(char)+5);

你需要为5个字符添加空间:4个用于".tde"后缀,还有一个用于'\0'终止符。你当前的代码只分配了4个字符的空间,因此最后一次写入是在你为新文件名分配的块的紧随其后的位置(即它的0字节之后)。

注意:你的代码存在一个常见问题——将realloc的结果直接分配给正在重新分配的指针。当realloc成功时这没问题,但失败时会造成内存泄漏。修复此错误需要将realloc的结果存储在单独的变量中,并在将值返回给*filename之前检查它是否为NULL

char *tmp = (char*)realloc(*filename, strlen(*collection_name)*sizeof(char)+5);
if (tmp != NULL) {
    *filename = tmp;
} else {
    // Do something about the failed allocation
}

直接对*filename进行赋值会导致内存泄漏,因为*filename指向的指针在失败时被覆盖,从而无法恢复。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - oz123
9
好的,你已经预料到了。因此,“0 bytes after”是Valgrind开发人员用通俗易懂的语言告诉我:“它是相邻的”,或者“直接在其后面”的方式。 - oz123
1
@Oz123:0字节后42字节后一样正确,不是吗? - alk
12
假设块从0x8000开始,长度为8个字节。那么块的最后一个有效地址将是0x8007,而0x8008将是其之后第一个无效的地址。当valgrind在0x8008处看到一个写入时,它会将其报告为对非法块的初始字节的写入,就像它是一个字节数组一样,并使用零基索引来报告偏移量。 - Sergey Kalinichenko
@Ac3_DeXt3R "相似"这个词太笼统了。请提出一个新问题并提供所有细节。 - Sergey Kalinichenko
@dasblinkenlight,是的,但它会被标记为重复! - Dr. Essen

6

我刚刚遇到了这个问题,因为我更改了一个类(添加了一个字段,所以我改变了它的大小),但是没有重新构建包含该头文件的所有源代码。因此,一些模块仍然尝试使用旧的大小。


4

分享我对这个问题的了解。

首先,错误信息是具有误导性的,特别是单词after。一开始,我以为它意味着在我分配内存块之后(在时间层面上)发生了某些事情。但实际上,它意味着您正在从某个随机内存地址读取数据。而那个随机内存地址恰好位于您分配的内存块之后(在地址空间中)。

根据我的经验,通常发生在数组索引超出范围时。

让我们看下面这个简单的演示代码:

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

int main() {
    int *x = (int*)malloc(sizeof(int));
    x += 3; // x now points to invalid memory(some random location)
    printf("%d\n", *x); // read from an invalid location of memory
    *x = 4;             // write to an invalid location of memory
    free(x - 3);
    return EXIT_SUCCESS;

}

在运行valgrind内存检查时,会产生以下错误信息。
==4720== Invalid read of size 4
==4720==    at 0x1091AC: main (memo2.c:7)
==4720==  Address 0x4a5204c is 8 bytes after a block of size 4 alloc'd
==4720==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==4720==    by 0x10919E: main (memo2.c:5)
==4720==
0
==4720== Invalid write of size 4
==4720==    at 0x1091C5: main (memo2.c:8)
==4720==  Address 0x4a5204c is 8 bytes after a block of size 4 alloc'd
==4720==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==4720==    by 0x10919E: main (memo2.c:5)

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