我对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报告的地址。怎么能和解呢?
答案 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
根据需要复制项目。哦,你正在压缩队列。嗯,这只需要使用新地址。使用%
模块化寻址时,请确保在复制之前不要覆盖该元素。