xunbei100 Ответов: 8

Удаление памяти, которая была выделена в DLL, приводит к сбою


Дорогие все,

У меня есть два модуля, т. е., main.exe и A.dll. У меня есть функция, объявленная в классе, т. е., ClassA в A.dll следующим образом:

char* ClassA::ReadFile(const char *szFilePath)
{
    int length;
    char* buffer;
    if(!IsFileExist(szFilePath)) {
        return NULL;
    }
    std::ifstream is;
    is.open (szFilePath, std::ios::binary);

    // get length of file:
    is.seekg (0, std::ios::end);
    length = is.tellg();
    is.seekg (0, std::ios::beg);

    // allocate memory:
    buffer = new char [length +1 ];             //mark line1
    memset(buffer,0,length + 1);               //mark line2

    // read data as a block:
    is.read (buffer,length);
    buffer[length] = '\0';
    is.close();
    return buffer;
}


В main.exe, Я вызываю функцию следующим образом:
ClassA* pA = GetClassInstanceA();

char* pszBuffTemp = pA->ReadFile();
delete[] pszBuffTemp;  // crash here!


Вот в чем проблема. Когда я запускаю main.exe, он приходит к сбою в строке "удалить". Но все в порядке после изменения "mark line1 и mark line2" выше следующим образом:

buffer = new char [length + 20];             //mark line1
memset(buffer,0,length + 20 );               //mark line2


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

////////////////////I tried it in the other sample as follows and the crash happened also///////////

////OutApi.h//////////

#ifndef _TEXT_OUTPUT_H_
#define _TEXT_OUTPUT_H_
#ifdef _WIN32
#	ifdef   TEXTDLL_EXPROTS
#		define  TEXTDLL_API   __declspec(dllexport)  
#	else
#		 define TEXTDLL_API   __declspec(dllimport)  
#	endif
#else
#	define TEXTDLL_API
#endif
#include <string>
class Iabc
{
public:
	Iabc() {}
	virtual ~Iabc() {}
	virtual char* GetText() = 0;
};
TEXTDLL_API Iabc* GetTestIns();
#endif

////////////////  CTest.H and CTest.CPP/////////

#ifndef _asdfasdfasf_H
#define _asdfasdfasf_H

#include "./OutApi.h"

class abc:  public Iabc
{
public:
    abc(){}
    ~abc(){}

    virtual char* GetText();
};

#endif

////////CTest.CPP/////////

#include "StdAfx.h"
#include "CTest.h"

char* abc::GetText()
{
    char* temp = "test code here ,if the problem still exists!";
    int len = sizeof(temp) + 20;
    char* pResult = new char[len];
    memset(pResult, 0 ,len);
    strcpy(pResult,  temp);
    return pResult;
}

Iabc* GetTestIns()
{
    static abc a;
    return &a;
}

//////////////the implematation of the exe ///////
#include "stdafx.h"
#include "OutApi.h"
#include <string>

#include <stdio.h>

#pragma comment(lib, "TextDll.lib")

int main(int argc, char* argv[])
{
    Iabc* abcObj = GetTestIns();
    char* pFromDll = abcObj->GetText();
    delete[] pFromDll;                    ///////crash here!

    int a;
    getchar(&a);
    return 0;
}


а информация о стеке вызовов выглядит следующим образом:

NTDLL! 7c92120e()
NTDLL! 7c98e576()
NTDLL! 7c9822e8()
KERNEL32! 7c85f9a7()
_CrtIsValidHeapPointer(const void * 0x00431800) line 1606
_free_dbg(void * 0x00431800, int 1) line 1011 + 9 bytes
operator delete(void * 0x00431800) line 49 + 16 bytes
main(int 1, char * * 0x00530e80) line 16 + 15 bytes
mainCRTStartup() line 206 + 25 bytes
KERNEL32! 7c817077()

Richard MacCutchan

Пробовали ли вы пройти через этот код с помощью отладчика, чтобы точно увидеть, что происходит? Вы уверены, что указатель, который вы передаете delete не был ли он каким-либо образом испорчен?

xunbei100

конечно, я прошел через этот код несколько раз и проверил содержимое памяти по значению указателя. и если я удалю массив в функции "readfile", то никаких проблем не возникнет.

Niklas L

Вы делаете много ненужных вызовов memset. Нет никакого смысла очищать буфер, который вы непосредственно заполняете символами, и там, где это необходимо, pBuffer[0] = 0 работает просто отлично.

8 Ответов

Рейтинг:
62

Cedric Moonen

Вы убедились, что бот hthe exe и dll используют одно и то же пространство кучи ? Это чрезвычайно важно, и если они этого не делают, это, безусловно, источник вашей проблемы.


Рейтинг:
2

Hristo-Bojilov

Насколько мне известно
массивы в C заканчиваются null, что означает, что

xunbei100 написал:
буфер[длина] = '\0';


очевидно, что это недопустимое назначение, потому что такого элемента массива нет, и это приводит к повреждению памяти.Возможно, вам следует использовать:

buffer = new char [length+1 ];  
memset(buffer,0,length );
delete[] buffer;


xunbei100

Большое спасибо! Я получил неправильный код выше, и я исправляю его fllow!

Рейтинг:
2

Aescleal

Вот вам совет-не используйте new и delete. Вы делаете кучу работы для себя, управляя размерами и тем, что нет, почему бы просто не использовать вектор? И вам не нужно вручную закрывать файлы, это одна из причин, по которой они являются объектами.

std::vector<char> read_file( const std::string &file_name )
{
    std::vector<char> buffer;
    std::ifstream file_stream( file_name.c_str() );

    if( file_stream )
    {
        std::streamsize file_length = file_stream.seekg( 0, std::ios::end ).tellg();
        file_stream.seekg( 0, std::ios::beg );

        buffer.resize( file_length );
        file_stream.read( &buffer[ 0 ], file_length );
    }

    return buffer;
}


Никаких утомительных возни с массивами и возни с распределителями памяти. И что еще более важно, он также безопасен для исключений, когда что-то идет не так.


xunbei100

Спасибо за предложение, это хорошая идея. И я все еще хочу знать, почему мой код потерпел крах.

Рейтинг:
2

Richard MacCutchan

Вы постоянно публикуете здесь разные фрагменты кода, так что невозможно узнать, что делает ваша настоящая программа; разве вы не можете сделать простое копирование и вставку?

Однако приведенный ниже код неверен в том, что вы использовали sizeof(temp) в качестве значения длины (а не strlen()), которая возвращает 4 (размер char*), в то время как фактическая строка имеет длину 44 символа, поэтому даже добавив 20, вы сразу же испортите свою память вызовом strcpy().

char* abc::GetText()
{
    char* temp = "test code here ,if the problem still exists!";
    int len = sizeof(temp) + 20;  // will return 24
    char* pResult = new char[len];
    memset(pResult, 0 ,len);
    strcpy(pResult,  temp); // oops, memory overwrite!!
    return pResult;
}


xunbei100

Ты ригт, ТКС ,я действительно сделал много ошибок, еще раз извини.
Я изменил "char* temp "на" char temp []", и результат тот же.
Может быть, Седрик прав, я постараюсь исправить это другими способами.

Спасибо еще раз.

Richard MacCutchan

- Я постараюсь исправить это другими способами." Я объяснил выше, что вам нужно сделать; Используйте правильный системный вызов, чтобы найти длину вашего буфера и убедиться, что вы впоследствии не перезаписываете пространство памяти, которое вам не принадлежит.

xunbei100

Следуя вашему предложению, я исправил свой код. И я совершенно уверен в том, в чем именно заключается моя проблема,
и проблема по-прежнему заключается в сбое, вызванном вызовом функции, которая возвращает указатель char, указывающий на блок памяти, выделенный в другой dll.
И я должен был проверить содержимое памяти по значению указателя, память правильная,и я могу получить правильный массив символов через вызов,
но это приводит к сбою, когда я удаляю указатель, чтобы освободить память сразу после вызова.
PS: эта проблема возникла только в отладочной версии, в релизной версии нет никаких проблем ,
в режиме отладки есть утверждение, и оно было опущено под выпуском ("_CrtIsValidHeapPointer" ),
Это проблема, которая меня смущает, если куча памяти недействительна, как я могу получить правильный массив символов?
Большое вам спасибо за помощь,у меня был большой урок, ^_^

Рейтинг:
1

xunbei100

Я нашел причину проблемы по подсказке, данной Седирком,
только установите "use run-time library "на" Debug multithreaded dll " в настройках VC6.0, чтобы вы могли создать новый блок памяти в dll и удалить его в другой dll, не встречая отладочного утверждения.


CPallini

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

JackDingler

Пожалуйста, не публикуйте дополнительные вопросы и информацию в качестве решений.

Вместо этого используйте ссылку "улучшить вопрос".

Размещая сообщения на панелях решений, вы показываете, что решили свою проблему, и объясняете, Как вы ее решили.

Перепечатывая различные ваши вопросы в качестве ответов, трудно понять, есть ли у вас все еще проблема и какую форму она принимает в настоящее время.

Рейтинг:
0

Junaij

[Сценарий]
Сбой происходит, когда приложение (EXE) пытается удалить память, выделенную другим модулем (DLL).

[Решение]
В этом случае у вас есть два менеджера кучи, конкурирующих друг с другом; один для EXE и один для DLL. Оба они независимы друг от друга, и оба ничего не знают друг о друге. Способ обойти это - использовать DLL-версию библиотек времени выполнения MFC. Таким образом, существует только один менеджер кучи для EXE и DLL, таким образом, проблема может быть решена. Но это решение добавляет некоторые ограничения к модулю DLL с точки зрения того, кто может использовать DLL. (продолжайте читать для получения более подробной информации).

Другим способом выделения или освобождения памяти является использование функций GlobalAlloc()/GlobalLock()/GlobalUnlock()/GlobalFree() с использованием флага GMEM_SHARE. Эти функции являются реальными вызовами Windows, которые получают "реальную" память от ОС. Поскольку все эти функции обходят диспетчер кучи для компилятора, любое приложение, написанное на любом языке, может вызвать функцию GlobalFree () на дескрипторе, который был выделен с помощью функции GlobalAlloc (). Это предпочтительный способ, которым DLL выделяет память для внешних модулей, если это зависит от внешнего модуля, чтобы освободить память.

Как обсуждалось в первом абзаце, если вы решите эту проблему, исправив "библиотеку времени выполнения", приложение VB не сможет использовать библиотеку DLL, поскольку оно не знает, что такое "новый" и "удалить"... Поэтому рекомендуется использовать функции "Global..." для управления памятью (разделяемой между модулями).

Другим решением этой проблемы является предоставление интерфейса ReleaseMemory в общей библиотеке DLL.

Выберите тот, который лучше всего подходит для вашего приложения...


Рейтинг:
0

Cedric Moonen

Когда вы выделяете свой массив, вы забыли принять во внимание нулевое завершение строки. Вам нужно выделить свой массив размером length+1.
Помните, что массивы в C++ основаны на нуле, а это означает, что при выделении массива из 10 элементов последний элемент имеет индекс 9, а не 10.


xunbei100

Мне очень жаль , что мой кодовый пост выше был неправильным ,дело в том, что я действительно считал нулевое завершение массива и мой правый код как fllow:

buffer = new char [length +1 ]; / / mark line1
memset(buffer, 0, length + 1); / / mark line2

и проблема все еще существует!

Cedric Moonen

Тогда может случиться так, что ваш exe-файл и ваша dll-библиотека не используют одну и ту же кучу. Убедитесь, что они оба используют одну и ту же версию библиотеки времени выполнения C (убедитесь, что и DLL, и exe имеют одинаковое значение для параметра "библиотека времени выполнения" в настройках проекта.

xunbei100

У меня будет немного более простой образец, чтобы проверить проблему и убедиться, что если проблема все еще существует.
спасибо вам за помощь.

Рейтинг:
0

xunbei100

Я так сожалею о своем кодовом посте выше, правильный код ClassA как fllow:

<pre lang="cs">char* ClassA::ReadFile(const char *szFilePath)
{
    int length;
    char* buffer;
    if(!IsFileExist(szFilePath)) {
        return NULL;
    }
    std::ifstream is;
    is.open (szFilePath, std::ios::binary);

    // get length of file:
    is.seekg (0, std::ios::end);
    length = is.tellg();
    is.seekg (0, std::ios::beg);

    // allocate memory:
    buffer = new char [length + 1]; //mark line1  here's length + 1
    memset(buffer,0,length + 1);    //mark line2

    // read data as a block:
    is.read (buffer,length);
    buffer[length] = '\0';
    is.close();
    return buffer;
}




и проблема существует та же самая


BillW33

Вы не должны публиковать комментарий к решению или дополнение к вашему вопросу как "решение". Либо добавьте комментарий к предыдущему решению, нажав кнопку "есть вопрос или комментарий?" нажмите кнопку или обновите свой вопрос с помощью кнопки "улучшить вопрос".