Нарушение доступа с помощью LocalAlloc () - PullRequest
1 голос
/ 11 мая 2010

У меня есть приложение Visual Studio 2008 для Windows Mobile 6 C ++, использующее API, для которого требуется LocalAlloc(). Чтобы сделать мою жизнь проще, я создал реализацию стандартного распределителя, который внутренне использует LocalAlloc ():

/// Standard library allocator implementation using LocalAlloc and LocalReAlloc 
/// to create a dynamically-sized array. 
/// Memory allocated by this allocator is never deallocated. That is up to the
/// user.
template< class T, int max_allocations > 
class LocalAllocator
{
public:
    typedef T         value_type;
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;

    pointer address( reference r ) const { return &r; };
    const_pointer address( const_reference r ) const { return &r; };

    LocalAllocator() throw() : c_( NULL )
    {
    };

    /// Attempt to allocate a block of storage with enough space for n elements
    /// of type T. n>=1 && n<=max_allocations.
    /// If memory cannot be allocated, a std::bad_alloc() exception is thrown.
    pointer allocate( size_type n, const void* /*hint*/ = 0 )
    {
        if( NULL == c_ )
        {   
            c_ = LocalAlloc( LPTR, sizeof( T ) * n );
        }
        else
        {
            HLOCAL c = LocalReAlloc( c_, sizeof( T ) * n, LHND );
            if( NULL == c )
                LocalFree( c_ );
            c_ = c;
        }
        if( NULL == c_ )
            throw std::bad_alloc();
        return reinterpret_cast< T* >( c_ );
    };

    /// Normally, this would release a block of previously allocated storage.
    /// Since that's not what we want, this function does nothing.
    void deallocate( pointer /*p*/, size_type /*n*/ )
    {
        // no deallocation is performed. that is up to the user.
    };

    /// maximum number of elements that can be allocated
    size_type max_size() const throw() { return max_allocations; };

private:
    /// current allocation point
    HLOCAL c_;
}; // class LocalAllocator

Мое приложение использует эту реализацию распределителя в std :: vector <>

#define MAX_DIRECTORY_LISTING 512

std::vector< WIN32_FIND_DATA, 
    LocalAllocator< WIN32_FIND_DATA, MAX_DIRECTORY_LISTING > > file_list;

WIN32_FIND_DATA find_data = { 0 };
HANDLE find_file = ::FindFirstFile( folder.c_str(), &find_data );
if( NULL != find_file )
{
    do 
    {
        // access violation here on the 257th item.
        file_list.push_back( find_data );
    } while ( ::FindNextFile( find_file, &find_data ) );

    ::FindClose( find_file );
}

// data submitted to the API that requires LocalAlloc()'d array of WIN32_FIND_DATA structures
SubmitData( &file_list.front() );

На 257-м элементе, добавленном в вектор <>, происходит сбой приложения с нарушением прав доступа:

Data Abort: Thread=8e1b0400 Proc=8031c1b0 'rapiclnt'
AKY=00008001 PC=03f9e3c8(coredll.dll+0x000543c8) RA=03f9ff04(coredll.dll+0x00055f04) BVA=21ae0020 FSR=00000007
First-chance exception at 0x03f9e3c8 in rapiclnt.exe: 0xC0000005: Access violation reading location 0x01ae0020.

LocalAllocator::allocate вызывается с n=512 и LocalReAlloc() успешно. Фактическое исключение нарушения прав доступа возникает в коде std :: vector <> после вызова LocalAllocator::allocate:

     0x03f9e3c8    
     0x03f9ff04    
>    MyLib.dll!stlp_std::priv::__copy_trivial(const void* __first = 0x01ae0020, const void* __last = 0x01b03020, void* __result = 0x01b10020) Line: 224, Byte Offsets: 0x3c    C++
     MyLib.dll!stlp_std::vector<_WIN32_FIND_DATAW,LocalAllocator<_WIN32_FIND_DATAW,512> >::_M_insert_overflow(_WIN32_FIND_DATAW* __pos = 0x01b03020, _WIN32_FIND_DATAW& __x = {...}, stlp_std::__true_type& __formal = {...}, unsigned int __fill_len = 1, bool __atend = true) Line: 112, Byte Offsets: 0x5c    C++
     MyLib.dll!stlp_std::vector<_WIN32_FIND_DATAW,LocalAllocator<_WIN32_FIND_DATAW,512> >::push_back(_WIN32_FIND_DATAW& __x = {...}) Line: 388, Byte Offsets: 0xa0    C++
     MyLib.dll!Foo(unsigned long int cbInput = 16, unsigned char* pInput = 0x01a45620, unsigned long int* pcbOutput = 0x1dabfbbc, unsigned char** ppOutput = 0x1dabfbc0, IRAPIStream* __formal = 0x00000000) Line: 66, Byte Offsets: 0x1e4    C++

Если кто-нибудь может указать, что я могу делать неправильно, я был бы признателен.

Спасибо, PaulH

Ответы [ 3 ]

2 голосов
/ 11 мая 2010

Сначала я подумал, что вашей проблемой является параметр от LHND до LocalReAlloc() - обычно этот параметр не следует передавать этой функции.

Реальная проблема в том, что вам даже не следует вызывать эту функцию. Реализация vector перераспределяет собственную память. Стандартный распределитель C ++ не обеспечивает перераспределение.

Вы должны реализовать:

template<typename T> class LocalAllocator {
...
pointer allocate(sizetype s)
{
    pointer p = reinterpret_cast<pointer>(LocalAlloc(LPTR, s * sizeof(T)));

    if (NULL == p)
        throw std::bad_alloc();

    return p;
}

void deallocate(pointer p, sizetype)    
{
    LocalFree(reinterpret_cast<LHND>(p));
}
...
}

Нечто подобное должно работать.

Вам не нужно отслеживать указатель - он будет возвращен вам при вызове deallocate(), за который отвечает ваш клиент API, реализация vector<T>.

2 голосов
/ 11 мая 2010

Я не уверен, как это должно работать. Когда вы выделяете каждый следующий блок, вы освобождаете текущий используемый блок. Таким образом, все указатели, которые указали там, становятся недействительными. Я сомневаюсь, что это то, что вектор ожидает от распределителя.

1 голос
/ 11 мая 2010

Чтобы расширить на ответ Игоря Кривокона , каждый вызов allocate(), возможно, делает недействительными все указатели, возвращенные предыдущими вызовами allocate(). Это не то, как распределитель должен работать и, скорее всего, приведет к неопределенному поведению:

Например, когда вектору нужно выделить новый больший объем памяти, он скопирует данные из «старой» памяти в «новую», возвращаемую allocate(). Затем он уничтожит все объекты в «старой» памяти и освободит ее.

Если old и new теперь являются одинаковыми ячейками памяти, копирование конструкции «новых» объектов и последующее уничтожение «старых» объектов приведут к путанице. С другой стороны, если old больше не действителен (потому что он перераспределен), вектор получит доступ к недопустимой памяти.

И еще один момент: Распределители должны быть копируемыми. Если копия создается из вашего распределителя, а затем оба используются для размещения дополнительных элементов, LocalReAlloc() будет вызываться для одного и того же указателя дважды. Поэтому вам нужно будет предоставить собственный конструктор копирования, который позволит избежать подобных проблем.

...