Valgrind没有检测到危险的释放记忆

我正在学习valgrind框架,并决定在我自己的小测试案例中运行它。 这里是下面的程序,它强制从堆中删除额外的对象(我在AMD64 / LINUX上运行它):

#include <iostream>
using namespace std;

struct Foo
{
    Foo(){ cout << "Creation Foo" << endl;}
    ~Foo(){ cout << "Deletion Foo" << endl;}
};

int main()
{
    Foo* ar = new Foo[3];
    *(reinterpret_cast<int*>(ar)-2) = 4;
    delete[] ar;
    return 0;
}

但valgrind的执行结果让我很困惑:

$ valgrind --leak-check = full ./a.out -v

== 17649 == Memcheck,一个内存错误检测器

== 17649 ==版权所有(C)2002-2017,和GNU GPL'd,Julian Seward等人。

== 17649 ==使用Valgrind-3.13.0和LibVEX; 用-h重新运行版权信息

== 17649 ==命令:./a.out -v

== == 17649

Creation Foo

Creation Foo

Creation Foo

删除Foo

删除Foo

删除Foo

删除Foo

== == 17649

== 17649 ==总结:

== 17649 ==在退出时使用:1块中72,704字节

== 17649 ==总堆使用情况:分配3个分配,2个释放,73,739个字节

== == 17649

== 17649 ==泄漏摘要:

== 17649 ==绝对丢失:0个块中的0个字节

== 17649 ==间接丢失:0个字节0个字节

== 17649 ==可能丢失:0块中的0个字节

== 17649 ==仍然可达:1块中72,704字节

== 17649 ==被抑制:0个字节0个字节

== 17649 ==没有显示可达到的块(找到指针的块)。

== 17649 ==要看到它们,请重新运行:--leak-check = full --show-leak-kinds = all

== == 17649

== 17649 ==对于检测和抑制错误的计数,请重新运行:-v

== 17649 ==错误摘要:来自0个上下文的0个错误(被抑制:0从0)

似乎valgrind(版本3.13.0)没有检测到任何内存损坏?

UPD:我编译main.cpp与命令g++ -g main.cpp


Valgrind没有检测到数组“前缀”的改变,因为它是内存的有效部分。 尽管它不应该被用户代码直接改变,但它仍然可以通过数组构造函数代码访问和修改,并且valgrind不提供如此精细的访问检查分离。 还要注意,这种腐败似乎并没有损坏堆,因此取消分配成功。

Valgrid没有检测到无效对象上的析构函数调用,可能是因为此调用实际上并未访问无效存储。 添加一些类字段将改变情况:

struct Foo
{
    int i;
    Foo(): i(0) { cout << i << "Creation Foo" << endl;}
   ~Foo(){ cout << i << "Deletion Foo" << endl;}
};

无效的大小为4的读取


Valgrind没有检测到内存问题,因为没有。

让我们一步一步地浏览你的程序(这是依赖于实现的,但它基本上是如何用于gcc和其他主要编译器的):

调用new Foo[3]

  • 8+3*sizeof(Foo)字节的内存被分配,我们称它为指针p 。 需要8个字节来存储数组中的元素数量。 当调用delete时,我们将需要这个号码。
  • 数组中的对象数保存到p[0]=3
  • 为内存地址p+8p+8+sizeof(Foo)p+8+2*sizeof(Foo)调用放置new运算符Foo() p+8+2*sizeof(Foo) ,即创建3个对象。
  • ar的地址为p+8并指向第一个Foo
  • 操作对象数*(reinterpret_cast<int*>(ar)-2) = 4

  • 好的, p[0]现在是4 。 大家都认为阵列中有4物体(但实际上只有3
  • 注意:如果Foo会有一个微不足道的析构函数(像int ),情况会有所不同,并且访问ar-8将是一个无效访问。

    在这种情况下,编译器会优化析构函数的调用,因为不需要做任何事情。 但是不需要记住元素的数量 - 所以p实际上是ar ,开始时没有偏移量/额外的8个字节。

    这就是为什么大多数编译器错误的代码:

    int *array=new int[10];
    delete array;//should be delete [] array;
    

    工作没有问题:内存管理器不需要知道指针后面有多少内存,无论它是只有一个int还是多个 - 它只是释放内存。

    调用delete [] ar

  • 析构函数被称为p[0]=4次,也用于arr[0], arr[1], arr[2]arr[3] 。 调用它的arr[3]是未定义的行为,但没有什么不好的事情发生:调用析构函数不会释放内存(或者甚至在你的情况下触摸它)。 它只打印一些东西 - 没有错。
  • 释放阵列内存。 实际上, p指针被释放而不是ar因为内存管理器只“知道” p我们可以从ar计算p 。 被称为空洞free(p)地方 - 没有人关心它拥有多少内存 - 并且使用的operator delete(*void)不提供它。
  • 没有什么,Valgrind的观点是什么问题。


    为了使我的观点更清晰(请参阅此处汇编的结果):

    Foo f;
    

    会导致只调用析构函数(无内存访问)但不释放内存 - 这就是在你的程序中发生的对象arr[0]arr[1]arr[2]arr[3]

    call    Foo::~Foo()
    

    Foo *f=new Foo();
    delete f;
    

    会导致调用析构函数和操作符delete,这会删除堆上的内存:

        call    Foo::~Foo()
        movq    %rbp, %rdi
        call    operator delete(void*) ; deletes memory, which was used for f
    

    然而,在你的情况下,不会为每个对象调用delete操作符,因为内存也不是按位分配的,而是作为整个内存块,即p


    如果你想调用delete ar; 而不是delete [] ar; 你可以看到会发生什么:

  • 仅为第一个Foo调用析构函数。
  • 程序将尝试释放指针arr而不是指针p 。 然而指针ar对于内存管理器来说是未知的(它只知道p ),这是有问题的。

  • 正如VTT指出的那样,如果析构函数触及对象中的某些内存,您将看到无效的内存访问超出数组的内存。

    如果你的析构函数必须释放一些内存(例如有一个向量作为成员),并因此将随机存储器内容解释为地址并对这些随机地址调用操作符delete ,则会得到错误。

    链接地址: http://www.djcxy.com/p/64957.html

    上一篇: Valgrind does not detect dangerous freeing memory

    下一篇: C valgrind memory leakage