重新分配已删除的数组并读取自由内存

时间:2014-12-29 05:10:29

标签: c++ memory-management valgrind

我对C ++和Valgrind比较陌生。我在mac上开发,所以我很有可能访问我不应该访问的内存,程序也不会崩溃。以前,我为基于数组的列表数据结构编写了一些代码,如C ++向量。我扩展了该结构以创建基于数组的循环队列。我在Xcode和终端中运行了单元测试,并没有遇到任何问题。

另一方面,Valgrind的代码存在问题。我相信我已经将问题缩小到以下代码,主要用于在数组变满时调整数组大小。

(我来自Java,请原谅这种风格)

private:

/**
 * the maximum number of elements the current list can contain if it is not
 * resized
 */
int m_arraySize;

/**
 * the actual list of elements in this list
 */
E* m_values;


/**
 * Copies the contents of this array into a new array and replaces the old
 * array-based list with the new array. If the new array has smaller size
 * than the old array, then elements that do not fit into the new array
 * are discarded. If the new array size is less than 1, 1 is automatically
 * used as the new array size
 *
 * @param newArraySize       the new size of the elements array
 */
void useResizedArray( int newArraySize ) {
    int arraySize = newArraySize;
    if ( arraySize < 1 ) {
        arraySize = 1;
    }
    E* newArr = new E[ arraySize ];

    //copy elements into the new array
    int smallestSize = min( m_arraySize , arraySize );
    for ( int i=0 ; i<smallestSize ; i++ ) {
        newArr[ i ] = m_values[ i ];
    }

    //free memory of the old array
    delete[] m_values;

    //update the list to have the new array and the new size
    m_values = newArr;
    m_arraySize = arraySize;
}

当我为基于数组的列表运行单元测试时,它们都通过了,Valgrind根本没有抱怨。我还将基于数组的列表子类化以创建基于循环阵列的队列。当我为队列运行我的单元测试时,它们都通过并且没有内存泄漏;然而,Valgrind说我经常访问免费内存。第一个错误报告如下(除了前两个测试之外,我已经抑制了所有测试,这导致了第一个错误):

==17904== Invalid read of size 4
==17904==    at 0x10001CA34: ArrayList<int>::set(int, int const&) (ArrayList.h:286)
==17904==    by 0x1000727E3: ArrayQueue<int>::offer(int const&) (ArrayQueue.h:126)
==17904==    by 0x10006191D: ArrayQueueTests::testOffer() (ArrayQueueTests.cpp:49)
==17904==    by 0x100060C47: ArrayQueueTests::test() (ArrayQueueTests.cpp:13)
==17904==    by 0x100001122: main (main.cpp:41)
==17904==  Address 0x1000e6c90 is 0 bytes inside a block of size 4 free'd
==17904==    at 0x4D9D: free (vg_replace_malloc.c:477)
==17904==    by 0x10001D416: ArrayList<int>::useResizedArray(int) (ArrayList.h:86)
==17904==    by 0x10007326E: ArrayList<int>::ensureCapacity(int) (ArrayList.h:377)
==17904==    by 0x1000727B1: ArrayQueue<int>::offer(int const&) (ArrayQueue.h:124)
==17904==    by 0x10006191D: ArrayQueueTests::testOffer() (ArrayQueueTests.cpp:49)
==17904==    by 0x100060C47: ArrayQueueTests::test() (ArrayQueueTests.cpp:13)
==17904==    by 0x100001122: main (main.cpp:41)
==17904== 
ArrayQueue unit tests finished. Passed 2 of 2 tests.
==17904== 
==17904== HEAP SUMMARY:
==17904==     in use at exit: 29,316 bytes in 377 blocks
==17904==   total heap usage: 482 allocs, 105 frees, 36,032 bytes allocated
==17904== 
==17904== LEAK SUMMARY:
==17904==    definitely lost: 0 bytes in 0 blocks
==17904==    indirectly lost: 0 bytes in 0 blocks
==17904==      possibly lost: 0 bytes in 0 blocks
==17904==    still reachable: 4,096 bytes in 1 blocks
==17904==         suppressed: 25,220 bytes in 376 blocks
==17904== Rerun with --leak-check=full to see details of leaked memory
==17904== 
==17904== For counts of detected and suppressed errors, rerun with: -v
==17904== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

在ArrayList.h:286,我执行简单语句

    m_values[ index ] = value;

检查它是否在基于数组的列表的(0<=index && index<m_arraySize)范围内。

Valgrind声称我在ArrayList.h:86中释放了内存,这是以下代码:

    delete[] m_values;

但我很确定在删除m_values之后我立即重新分配了它。所以,我到处玩,看看会发生什么。我注释了这一行

    delete[] m_values;

并且内存访问错误消失了;然而,在评论删除后,Valgrind检测到内存泄漏。因此,我得出结论,delete[] m_values导致Valgrind报告内存泄漏问题。

我想知道delete[]对指针m_values做了什么,不再允许我使用它。如果我不能delete[]指针,有没有办法避免内存泄漏?我也很困惑,因为我当前的搜索表明m_values可以在执行delete[] m_values后重新分配并重复使用。

我也在考虑它可能是我的代码的其他部分的问题,但如果它是导致Valgrind指出ArrayList.h:86的其他东西的副作用我会感到惊讶。

或者(非常不可能)可能是Valgrind的问题并且代码是正确的?删除它后,我已经尝试将乱码放入m_values数组,但在重新分配之前。这样,如果我确实访问了free'd内存,它就会在被释放之前被改变。单元测试仍然通过。

有什么东西似乎像一个明显的错误一样突出吗?感谢您的时间和帮助。

编辑:

我将添加更多代码。以下是ArrayList的相关部分。

的main.cpp

#include <iostream>
#include "ArrayList.h"
#include "ArrayQueue.h"
#include <string>
using std::string;

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

    string expected;
    string found;
    string errorMessage = "ArrayQueue offer() failed!";
    ArrayQueue< int > test;

    //test empty queue
    test.clear();
    expected = "[]";
    found = test.toString();
    //evaluateTest( expected , found , errorMessage );

    //test 1 element
    test.clear();
    test.offer( 1 );
    expected = "[1]";
    found = test.toString();
    //evaluateTest( expected , found , errorMessage );

    //test multiple elements
    test.clear();
    test.offer( 1 );
    test.offer( 2 ); //  <----- This line causes bad memory access
}

ArrayList.h

#ifndef Testing_ArrayList_h
#define Testing_ArrayList_h

#include <iostream>
using std::cout;
using std::endl;

#include <string>
using std::string;

using std::runtime_error;

using std::min;

template< typename E >
class ArrayList {

    friend class ArrayListTests;

private:

    /**
     * the maximum number of elements the current list can contain if it is not
     * resized
     */
    int m_arraySize;

    /**
     * the number of elements this list currently contains
     */
    int m_numElements;

    /**
     * the actual list of elements in this list
     */
    E* m_values;

    /**
     * Copies the contents of this array into a new array and replaces the old
     * array-based list with the new array. If the new array has smaller size
     * than the old array, then elements that do not fit into the new array
     * are discarded. If the new array size is less than 1, 1 is automatically
     * used as the new array size
     *
     * @param newArraySize       the new size of the elements array
     */
    void useResizedArray( int newArraySize ) {
        int arraySize = newArraySize;
        if ( arraySize < 1 ) {
            arraySize = 1;
        }
        E* newArr = new E[ arraySize ];

        cout << "Allocated " << newArr << endl;


        //copy elements into the new array
        int smallestSize = min( m_arraySize , arraySize );
        for ( int i=0 ; i<smallestSize ; i++ ) {
            newArr[ i ] = m_values[ i ];
        }

        //free memory of the old array
        cout << "Deleting " << m_values << endl;
        delete[] m_values;

        //update the list to have the new array and the new size
        m_values = newArr;
        m_arraySize = arraySize;
    }

    /**
     * Determines if the given index is out of bounds or not
     *
     * @param index                 an index
     * @return                      if the given index is out of bounds (greater
     *                              than the maximum element index, or less than
     *                              the minimum element index)
     */
    bool isOutOfBounds( int index ) const {
        return ( index < 0 || index >= m_numElements );
    }

protected:

    /**
     * Gets the maximum number of elements the current array can contain
     *
     * @return                      the current size of the array of this list
     */
    int getArraySize() const {
        return m_arraySize;
    }

public:

    /**
     * Creates a default ArrayList with no elements
     */
    ArrayList() {
        m_arraySize = 1;
        m_numElements = 0;
        m_values = new E[ 1 ];

        cout << "Allocated " << m_values << endl;

    }

    /**
     * Copies the contents of the given ArrayList into this ArrayList
     *
     * @param l                     the ArrayList to be copied
     */
    ArrayList( const ArrayList<E>& l ) {
        m_arraySize = l.m_arraySize;
        m_numElements = l.m_numElements;
        m_values = new E[ m_arraySize ];

        cout << "Allocated " << m_values << endl;

        for ( int idx=0 ; idx<l.size() ; idx++ ) {
            m_values[ idx ] = l.get( idx );
        }
    }

    /**
     * Creates an ArrayList with the given initial size. If the given
     * initial size is less than 1, then 1 is automatically used
     * as the initial size.
     *
     * @param initialSize           initial number of array slots for
     *                              storing elements of the list
     */
    ArrayList( int initialSize ) {
        int arraySize = initialSize;
        if ( arraySize < 1 ) {
            arraySize = 1;
        }
        m_arraySize = arraySize;
        m_numElements = 0;
        m_values = new E[ arraySize ];

        cout << "Allocated " << m_values << endl;

    }

    ~ArrayList< E >() {

        cout << "Deleting " << m_values << endl;

        delete[] m_values;
    }

    /**
     * Copies the contents of the given ArrayList into this ArrayList
     */
    ArrayList<E>& operator=( const ArrayList<E>& listToCopy ) {
        m_arraySize = listToCopy.m_arraySize;
        m_numElements = listToCopy.m_numElements;
        E* newArr = new E[ m_arraySize ];

        cout << "Allocated " << newArr << endl;

        for ( int idx=0 ; idx<m_numElements ; idx++ ) {
            newArr[ idx ] = listToCopy.m_values[ idx ];
        }

        cout << "Deleting " << m_values << endl;

        delete[] m_values;
        m_values = newArr;
        return *this;
    }

    /**
     * Gets the element at the given index
     *
     * @param index             the index of an element in the list
     * @return                  the element at the given index in the list
     * @throws error            if the index given is out of bounds
     */
    E& get( int index ) const {

        //make sure not we are not asked to go out of bounds
        if ( isOutOfBounds( index ) ) {
            throw runtime_error(
                                generateAccessOutOfBoundsMessage( index , m_numElements ) );
        }
        return m_values[ index ];
    }

    /**
     * Inserts the given element at the specified index
     *
     * @param index             the index in the ArrayList at which to insert
     *                          the given value
     * @param value             the value to insert
     * @throws error            if the index is out of bounds
     */
    void insert( int index , const E& value ) {

        //make sure we aren't asked to insert the value out of bounds
        //we allow insertion at the index following the last element
        //of the array, as that is equivalent to appending to the end
        if ( index < 0 || index > m_numElements ) {
            throw std::runtime_error(
                                     "insert out of bounds" );
        }

        //resize the elements array if we are over capacity
        //we resize it so that after the insert operation, we have exactly
        //twice as many empty slots as elements
        if ( m_numElements+1 > m_arraySize ) {
            useResizedArray( (m_numElements)*2 );
        }

        //shift all the elements including and after the given index
        //down by 1
        for ( int i=m_numElements-1 ; i>=index ; i-- ) {
            m_values[ i+1 ] = m_values[ i ];
        }

        //insert the new value
        m_values[ index ] = value;
        m_numElements++;
    }

    /**
     * Sets the given index of the list to contain the given element, and if
     * necessary, replaces an element that is currently there
     *
     * @param index             the index at which to set the new value
     * @param value             the new value to be set
     * @throws error            if the index is out of bounds
     */
    void set( int index , const E& value ) {
        if ( isOutOfBounds( index ) ) {
            throw std::runtime_error(
                                     generateAccessOutOfBoundsMessage(
                                                                                   index , m_numElements ) );
        }

        cout << "Writing to m_values at address " << &m_values[ index ] << endl;
        m_values[ index ] = value;
        cout << "Finish writing to m_values at address " << &m_values[ index ] << endl;
    }


    /**
     * Adds the given element to the end of the list
     *
     * @param value             the element to add
     */
    void append( const E& value ) {
        insert( m_numElements , value );
    }

    /**
     * Removes all elements from the ArrayList
     */
    void clear() {
        useResizedArray( 0 );
        m_numElements = 0;
        m_arraySize = 0;
    }

    int size() const {
        return this->m_numElements;
    }

    /**
     * Ensures that the ArrayList can contain the given number of elements
     * without resizing
     *
     * @param capacity          a minimum number of elements the list must
     *                          be able to contain without resizing
     */
    void ensureCapacity( int capacity ) {
        if ( capacity > m_arraySize ) {
            useResizedArray( capacity );
        }
    }

    string generateAccessOutOfBoundsMessage( int index , int size ) const { return ""; }

    string toString() const { return ""; }
};

#endif

ArrayQueue.h

#ifndef Testing_ArrayQueue_h
#define Testing_ArrayQueue_h

template< typename E >
class ArrayQueue : protected ArrayList< E > {

    friend class ArrayQueueTests;

private:

    /**
     * the index of the first element in the queue, which will be the first
     * element polled
     */
    int m_headIdx;

    /**
     * the index of the last element in the queue PLUS ONE.
     */
    int m_tailIdx;

    /**
     * the number of elements in the queue. we must store the number of elements
     * and not just calculate it from the index of the head and tail due to
     * wrap-around problems. if the head and tail indices are the same
     * then we cannot be sure if the queue has size 0, or if it has
     * reached full capacity.
     */
    int m_numElements;

    /**
     * Determines if the given index is out of bounds in the queue
     *
     * @return                  if the given element is
     */
    bool isOutOfBounds( int index ) const {
        return index < 0 || index >= m_numElements;
    }

    /**
     * Determines the index in the queue's underlying array, given the index
     * of an element in the queue
     *
     * @return                  the index in the queue converted to the index
     *                          in the array
     */
    int getIndexInArray( int queueIndex ) const {

        //queue size of 0 is special case, because we can't really define
        //wrapping around
        if ( m_numElements == 0 ) {
            return 0;
        }


        if ( queueIndex >= 0 ) {
            return (m_headIdx+queueIndex) % ArrayList< E >::getArraySize();
        }
        else {

            //if we are "unwrapping" around with a negative index
            //just keep adding the size of the array
            int wraparoundTimes = abs( queueIndex / m_numElements )+1;
            int offset = wraparoundTimes * m_numElements;
            return (m_headIdx + queueIndex + offset ) %
            ArrayList< E >::getArraySize();
        }
    }

public:
    /**
     * Constructs a default queue with no elements
     */
    ArrayQueue() : ArrayList< E >() {
        m_headIdx = 0;
        m_tailIdx = 0;
        m_numElements = 0;

        //we cannot have an array size of 0, because that would mess up
        //wrap around calculations
        ArrayList< E >::ensureCapacity( 1 );
    }

    /**
     * Puts the given value into the back of the queue
     *
     * @param value             the value to insert into the queue
     */
    void offer( const E& value ) {

        //if we exceed capacity, we might as well just resize the array
        //and start everything over with the head at the 0-th index
        if ( m_numElements+1 > ArrayList< E >::getArraySize() ) {
            E* tmp[ m_numElements ];
            for ( int i=0 ; i<m_numElements ; i++ ) {
                tmp[ i ] = &ArrayList< E >::get( (m_headIdx+i)%ArrayList< E >::getArraySize() );
            }
            ArrayList< E >::ensureCapacity( (m_numElements)*2 );
            for ( int i=0 ; i<m_numElements ; i++ ) {
                ArrayList< E >::set( i , *tmp[ i ] );
            }
            m_headIdx = 0;
            m_tailIdx = m_numElements;
        }

        //insert the element at the index of the tail
        int insertIdx = m_tailIdx % ArrayList< E >::getArraySize();

        //if the ArrayList representation already has an element at the
        //insertion index, just overwrite it
        if ( insertIdx < ArrayList< E >::size() ) {
            ArrayList< E >::set( insertIdx , value );
        }

        //if the ArrayList representation doesn't have an element at the
        //insertion index already, then we actually need to insert the
        //the new element. Otherwise, we'd get an access out of bounds
        //exception
        else {
            ArrayList< E >::insert( insertIdx , value );
        }

        m_tailIdx = (m_tailIdx+1) % ArrayList< E >::getArraySize();
        m_numElements++;
    }

    /**
     * Clears the queue so that it contains no elements
     */
    void clear() {
        ArrayList< E >::clear();
        m_headIdx = 0;
        m_tailIdx = -1;
        m_numElements = 0;

        //we cannot have an array size of 0 because that would mess up wrap
        //around calculations
        ArrayList< E >::ensureCapacity( 1 );
    }

    string toString() const { return ""; }
};


#endif

正如Ben Voigt所说,无论什么时候分配东西,我都会打印地址,每当删除某些内容时,我都会打印地址。 print语句现已合并到ArrayList.h示例中。我重新编译,并通过Valgrind,并获得以下输出:

==2207== Memcheck, a memory error detector
==2207== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==2207== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==2207== Command: ./a.out
==2207== 
Allocated 0x1000169f0
Allocated 0x100017a80
Deleting 0x1000169f0
Allocated 0x100017ad0
Deleting 0x100017a80
Allocated 0x100017b20
Deleting 0x100017ad0
Writing to m_values at address 0x100017b20
==2207== Invalid read of size 4
==2207==    at 0x100001640: ArrayList<int>::set(int, int const&) (ArrayList.h:251)
==2207==    by 0x100001243: ArrayQueue<int>::offer(int const&) (ArrayQueue.h:108)
==2207==    by 0x100000F99: main (main.cpp:38)
==2207==  Address 0x100017ad0 is 0 bytes inside a block of size 4 free'd
==2207==    at 0x4D9D: free (vg_replace_malloc.c:477)
==2207==    by 0x100001A4F: ArrayList<int>::useResizedArray(int) (ArrayList.h:73)
==2207==    by 0x1000014DD: ArrayList<int>::ensureCapacity(int) (ArrayList.h:287)
==2207==    by 0x100001211: ArrayQueue<int>::offer(int const&) (ArrayQueue.h:106)
==2207==    by 0x100000F99: main (main.cpp:38)
==2207== 
Finish writing to m_values at address 0x100017b20
Deleting 0x100017b20
==2207== 
==2207== HEAP SUMMARY:
==2207==     in use at exit: 29,316 bytes in 377 blocks
==2207==   total heap usage: 476 allocs, 99 frees, 35,952 bytes allocated
==2207== 
==2207== LEAK SUMMARY:
==2207==    definitely lost: 0 bytes in 0 blocks
==2207==    indirectly lost: 0 bytes in 0 blocks
==2207==      possibly lost: 0 bytes in 0 blocks
==2207==    still reachable: 4,096 bytes in 1 blocks
==2207==         suppressed: 25,220 bytes in 376 blocks
==2207== Rerun with --leak-check=full to see details of leaked memory
==2207== 
==2207== For counts of detected and suppressed errors, rerun with: -v
==2207== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

我看到Valgrind声称我写信给地址0x100017ad0,但是我的程序说它写了地址0x100017b20。是的,Valgrind说得对,0x100017ad0是免费的,因为我的程序也报告了这一点。但是,我的计划并未写入Valgrind报告的地址。怎么能和解呢?

1 个答案:

答案 0 :(得分:1)

是的,Valgrind 100%正确。这是ArrayQueue::offer中的问题:

E* tmp[ m_numElements ];
for ( int i=0 ; i<m_numElements ; i++ ) {
    tmp[ i ] = &ArrayList< E >::get( (m_headIdx+i)%ArrayList< E >::getArraySize() );
}

现在tmp包含一堆指向ArrayList内容的指针

ArrayList< E >::ensureCapacity( (m_numElements)*2 );

ArrayList可能已经在增加容量的过程中移动了它的内容。

for ( int i=0 ; i<m_numElements ; i++ ) {
     ArrayList< E >::set( i , *tmp[ i ] );
}

取消引用现在无效的指针,这会创建一个狂野引用(未定义的行为)。当set尝试使用该引用获取值时,BOOM!

我不确定该循环应该完成什么,因为ensureCapacity调用useResizedArray根据需要复制项目。哦,你正在压缩队列。嗯,这只需要使用新地址。使用%模块化寻址时,请确保在复制之前不要覆盖该元素。