Проблемы при освобождении памяти
От: wbear  
Дата: 21.07.22 11:45
Оценка:
Добрый день, коллеги!

Который день бьюсь с возникшей проблемой, в части освобождения памяти!

Опишу что происходит в рабочем проекте.
Суть проблемы в следующем, есть функция void test(VString &arg), в которой из ранее созданного объекта pObj(pObj это класс который является обверткой вокруг COM объекта) вызывается функция GetVersionSoft, которая возвращает указатель(LPCWSTR ) на строку(версия софта). В тестовой функции вызов функции pObj->GetVersionSoft(d); закомметарен. Затем в функции WideCharToVString мы конвертируем в строку и полученную строку записываем в
переменную VString* a2. Выходим из функции WideCharToVString. После чего проводим присваивание arg = v8. В процесс выхода из функции вызывается деструктор класса VString переменной v8. Внутри которого происходит освобождение памяти и при вызове функции free происходит ексепшен с кодом 0x80000003. Другие деструкторы класса VString отрабатываются без проблем.


Для воспроизведения ошибки написал тестовый пример(исходники привел ниже), отличие от рабочего проекта только в закомментаренном вызове функции pObj->GetVersionSoft(d). Ошибка не возникает.


Может кто подскажет в чем проблема с освобождение памяти. При условии что другая прога(исходников нет) которая работает с этим COM объектом отрабатывает без ошибок.


Исходный код класса VString и тестовый пример ниже.

VString.h
#ifndef __VSTRING_H__
#define __VSTRING_H__

#include <windows.h>

class VString
{
public:
    VString();
    VString(int );
    VString(char *pSrc);
    VString(VString &pStr);
    virtual ~VString();

    void Internal_delete(char *pData);
    char* Internal_new(unsigned int uiSize);

    char* c_str() const;

    unsigned int erase();    
    unsigned int length();

    VString& operator=( const VString &rh);
    VString& operator=( const char *szSrc);

    
    
    
public:
    char* m_pString;  //+4 указатель на строку
    int m_LenString;  //+8 дилина строки
    int m_CountChar;  //+C количество символов за минусом последнегох[\0 конец строки]
    CRITICAL_SECTION  m_CriticalSection;//+10

};


#endif


VString.cpp
#include "stdafx.h"
#include "malloc.h"
#include "vstring.h"

#include "memory.h"
#include "string.h"






VString::VString()
{
    m_pString =0;
    m_LenString = 0;
    m_CountChar = 0;
    
    InitializeCriticalSection(&this->m_CriticalSection);
}




VString::VString(int iSizeMem)
{
    this->m_LenString = 0;
    this->m_CountChar = 0;

    this->m_pString = this->Internal_new(iSizeMem);// (char*)malloc(iSizeMem);
    if(this->m_pString)
    {
        this->m_LenString = iSizeMem;

        memset(this->m_pString, 0, iSizeMem);
    }

    InitializeCriticalSection(&this->m_CriticalSection);
}




VString::VString(char *pSrc)
{
    this->m_pString =0;
    this->m_LenString = 0;
    this->m_CountChar = 0;
    
    InitializeCriticalSection(&this->m_CriticalSection);

    *this = pSrc;//גחגאול VString& VString::operator=( const char *szSrc)
}


VString::VString(VString &pStr)
{
    this->m_pString = 0;
    this->m_LenString = 0;
    this->m_CountChar = 0;
    
    InitializeCriticalSection(&this->m_CriticalSection);

    *this = pStr;//גחגאול VString& VString::operator=(const VString& rh)
}


void VString::Internal_delete(char *pData)
{
    if(pData != NULL)
    {
        free(pData);
        pData = 0;
    }
}


char* VString::Internal_new(unsigned int uiSize)
{
    return (char*)malloc(uiSize);    
}




VString::~VString()
{
    EnterCriticalSection(&this->m_CriticalSection);

    if(m_pString)
    {    
        this->Internal_delete(this->m_pString);

        this->m_LenString = 0;
        this->m_CountChar = 0;
    }

    LeaveCriticalSection(&this->m_CriticalSection);    
    DeleteCriticalSection(&this->m_CriticalSection);
}


char* VString::c_str() const
{
    if(m_pString)
        return  ( char*)m_pString;
    else
        return (  char* )"";
}



unsigned int VString::erase()
{
    if(this->m_pString != NULL)
    {
        this->m_pString = 0;//+4
        this->m_CountChar = 0;//+C 
    }

    return this->length();
}




unsigned int VString::length()
{
    return this->m_CountChar;
}


VString& VString::operator=(const VString& rh)
{
    *this = (char*)rh.c_str(); //גחגול VString& VString::operator=( const char *szSrc)

    return *this;
}


VString& VString::operator=( const char *szSrc)
{
    int iLen;
    EnterCriticalSection(&this->m_CriticalSection);


    if(szSrc && (iLen = strlen(szSrc)) !=0 )
    {
        if((this->m_pString) && (this->m_LenString<= iLen) )
        {
            this->Internal_delete(this->m_pString);
            
            this->m_pString = NULL;
            this->m_LenString = 0;
        }


        if( !(this->m_pString) )
            this->m_pString = this->Internal_new(iLen+1);


        if( this->m_pString )
        {
            this->m_LenString = iLen+1;
            strncpy(this->m_pString, szSrc, iLen);
            this->m_pString[iLen] = '\0';
            this->m_CountChar = iLen;

            LeaveCriticalSection(&this->m_CriticalSection);
        }
    }
    else
    {
        this->erase();
        LeaveCriticalSection(&this->m_CriticalSection);
    }

    return *this;
}


test.cpp
// tststr.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

#include <OBJBASE.h>

#include "VString.h"


void WideCharToVString( LPCWSTR lpWideCharStr, VString* a2, int a3)
{
    int cchMultiByte = wcslen(lpWideCharStr) + 1;

    char* lpMultiByteStr;
    lpMultiByteStr = new char[cchMultiByte];

    
    WideCharToMultiByte(0, 0, lpWideCharStr, -1, lpMultiByteStr, cchMultiByte, 0, 0);

    if ( a3 )
        CoTaskMemFree((LPVOID)lpWideCharStr);

    *a2 = lpMultiByteStr;

    delete(lpMultiByteStr);

}


void test(VString &arg)
{
    VString v8;

    LPCWSTR d = L"1.4.d";
    //pObj->GetVersionSoft(d);

    WideCharToVString( d, &v8, 1);

    arg = v8;
}




int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
     // TODO: Place code here.
    VString v;
    test(v);

    return 0;
}
Re: Проблемы при освобождении памяти
От: kov_serg Россия  
Дата: 21.07.22 12:25
Оценка:
Здравствуйте, wbear, Вы писали:

W>Который день бьюсь с возникшей проблемой, в части освобождения памяти!

void WideCharToVString( LPCWSTR lpWideCharStr, VString* a2, int a3) {
    int cchMultiByte = wcslen(lpWideCharStr) + 1;

    char* lpMultiByteStr;
    lpMultiByteStr = new char[cchMultiByte];

    WideCharToMultiByte(0, 0, lpWideCharStr, -1, lpMultiByteStr, cchMultiByte, 0, 0);

    if ( a3 )
        CoTaskMemFree((LPVOID)lpWideCharStr);

    *a2 = lpMultiByteStr;

    delete(lpMultiByteStr); // <<< (1) надо delete[] lpMultiByteStr;
}


void test(VString &arg) {
    VString v8;

    LPCWSTR d = L"1.4.d";
    //pObj->GetVersionSoft(d);

    WideCharToVString( d, &v8, 1); // <<< (2) - CoTaskMemFree для статической d зачем ?

    arg = v8;
}


ps: Нафига там CriticalSection?
Re: Проблемы при освобождении памяти
От: σ  
Дата: 21.07.22 12:43
Оценка:
>
    if(pData != NULL)
    {
        free(pData);

Re: Проблемы при освобождении памяти
От: B0FEE664  
Дата: 21.07.22 13:54
Оценка:
Здравствуйте, wbear, Вы писали:

W>Который день бьюсь с возникшей проблемой, в части освобождения памяти!

С таким кодом это надолго...

W>Для воспроизведения ошибки написал тестовый пример(исходники привел ниже), отличие от рабочего проекта только в закомментаренном вызове функции pObj->GetVersionSoft(d). Ошибка не возникает.

Это странно, потому что функция CoTaskMemFree получает указатель на константную строку L"1.4.d". что должно приводить к порче памяти, по идее.

W>Может кто подскажет в чем проблема с освобождение памяти. При условии что другая прога(исходников нет) которая работает с этим COM объектом отрабатывает без ошибок.

Скорее всего проблема связана с тем, как заказывается память для параметра GetVersionSoft.


PS В коде много ошибок, ключая утечку памяти и падение при некоторых сценариях использования VString.
И каждый день — без права на ошибку...
Re: Проблемы при освобождении памяти
От: Videoman Россия https://hts.tv/
Дата: 21.07.22 16:33
Оценка:
Здравствуйте, wbear, Вы писали:

W>переменную VString* a2. Выходим из функции WideCharToVString. После чего проводим присваивание arg = v8. В процесс выхода из функции вызывается деструктор класса VString переменной v8. Внутри которого происходит освобождение памяти и при вызове функции free происходит ексепшен с кодом 0x80000003. Другие деструкторы класса VString отрабатываются без проблем.


Даже если пропустить весь код, до WideCharToVString в котором масса нюансов, скажем так:
int cchMultiByte = wcslen(lpWideCharStr) + 1; // Вычисление длины буфера неправильно

Внимательно посмотри документацию на функцию WideCharToMultiByte — это "комбайн", который и преобразовывает широкую строку в нужную тебе кодировку и она же вычисляет размер необходимого буфера. У тебя в качестве кодировки указана ANSI, ты уверен что это то, что тебе нужно?
delete(lpMultiByteStr); // Удаление константного буфера, непонятно как выделенного !!!

У тебя в примере буфер выделен вообще на стеке. На лицо порча хипа и привет.
Re: Проблемы при освобождении памяти
От: AleksandrN Россия  
Дата: 21.07.22 22:19
Оценка:
А зачем критическая секция, объект класса используется в нескольких потоках? И зачем критическая секция в деструкторе, объект может удаляться из нескольких потоков???

W>    int cchMultiByte = wcslen(lpWideCharStr) + 1;
W>
W>    char* lpMultiByteStr;
W>    lpMultiByteStr = new char[cchMultiByte];


Если в lpWideCharStr есть символы с кодом больше 127 и конвертируется в UTF-8, то длины буфера не хватит.

unsigned int VString::erase()
{
    if(this->m_pString != NULL)
    {
        this->m_pString = 0;  // Приведёт к утечке памяти.
        this->m_CountChar = 0;
    }

    return this->length();
}


Почему потребовался свой велосипед вместо std::string?
Re[2]: Проблемы при освобождении памяти
От: wbear  
Дата: 22.07.22 09:53
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>ps: Нафига там CriticalSection?

Класс VString является частью большой библиотеки по работе с COM серверами и службами. В связи с этим и используется блокировка доступа.
Re[2]: Проблемы при освобождении памяти
От: wbear  
Дата: 22.07.22 10:04
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>С таким кодом это надолго...

Согласен что код корявый. Убрал из исходников все лишнее(проверки, код основных функций), чтобы можно было увидеть в чем может быть ошибка

BFE>Это странно, потому что функция CoTaskMemFree получает указатель на константную строку L"1.4.d". что должно приводить к порче памяти, по идее.

После константной строки находиться закомментаренная функция pObj->GetVersionSoft(d); Именно в этой функции я так понимаю какраз выделяется память и записывается строка с версией ПО.


W>>Может кто подскажет в чем проблема с освобождение памяти. При условии что другая прога(исходников нет) которая работает с этим COM объектом отрабатывает без ошибок.

BFE>Скорее всего проблема связана с тем, как заказывается память для параметра GetVersionSoft.


BFE>PS В коде много ошибок, ключая утечку памяти и падение при некоторых сценариях использования VString.

Согласен что код очень сырой. НО моя задача на текущем этапе в проекте помочь, людям создать из ассемблеровских фрагментов набросок будущей SDK.
Думаю что код после меня еще не раз измениться.

Если укажите возможные сценарии возникновения ошибки — буду благодарен.
Re[2]: Проблемы при освобождении памяти
От: wbear  
Дата: 22.07.22 10:59
Оценка: :)
Здравствуйте, AleksandrN, Вы писали:

AN>А зачем критическая секция, объект класса используется в нескольких потоках? И зачем критическая секция в деструкторе, объект может удаляться из нескольких потоков???

Класс VString является частью большой библиотеки по работе с COM серверами и службами. В связи с этим и используется блокировка доступа.


AN>
W>>    int cchMultiByte = wcslen(lpWideCharStr) + 1;
W>>
W>>    char* lpMultiByteStr;
W>>    lpMultiByteStr = new char[cchMultiByte];
AN>


AN>Если в lpWideCharStr есть символы с кодом больше 127 и конвертируется в UTF-8, то длины буфера не хватит.


AN>
AN>unsigned int VString::erase()
AN>{
AN>    if(this->m_pString != NULL)
AN>    {
        this->>m_pString = 0;  // Приведёт к утечке памяти.
        this->>m_CountChar = 0;
AN>    }

AN>    return this->length();
AN>}
AN>


AN>Почему потребовался свой велосипед вместо std::string?

Велосипед не мой. Это система импортная АСУ. Разраб АСУ за бугром, в связи с последними событиями выкатил отказ в поддержке ПО. Поставили задачу разобраться где и какие функции взаимодействуют с COM серверами И написать прототип будущего SDK. Вот и ковыряюсь.
Re: Проблемы при освобождении памяти
От: Chorkov Россия  
Дата: 22.07.22 11:59
Оценка:
Здравствуйте, wbear, Вы писали:

W>Добрый день, коллеги!


W>Который день бьюсь с возникшей проблемой, в части освобождения памяти!


W>Опишу что происходит в рабочем проекте.

W>Суть проблемы в следующем, есть функция void test(VString &arg), в которой из ранее созданного объекта pObj(pObj это класс который является обверткой вокруг COM объекта) вызывается функция GetVersionSoft, которая возвращает указатель(LPCWSTR ) на строку(версия софта). В тестовой функции вызов функции pObj->GetVersionSoft(d); закомметарен. Затем в функции WideCharToVString мы конвертируем в строку и полученную строку записываем в
W>переменную VString* a2. Выходим из функции WideCharToVString. После чего проводим присваивание arg = v8. В процесс выхода из функции вызывается деструктор класса VString переменной v8. Внутри которого происходит освобождение памяти и при вызове функции free происходит ексепшен с кодом 0x80000003. Другие деструкторы класса VString отрабатываются без проблем.


W>Для воспроизведения ошибки написал тестовый пример(исходники привел ниже), отличие от рабочего проекта только в закомментаренном вызове функции pObj->GetVersionSoft(d). Ошибка не возникает.



W>Может кто подскажет в чем проблема с освобождение памяти. При условии что другая прога(исходников нет) которая работает с этим COM объектом отрабатывает без ошибок.



W>Исходный код класса VString и тестовый пример ниже.


Что заметил:
1)
VString a("123456");
a=a; // сперва удалит строку, а потом попытается ее скопировать...


2)
Если Internal_new вернет nullptr, то мы никогда не выйде из критической секции:
        if( !(this->m_pString) )
            this->>m_pString = this->Internal_new(iLen+1);


        if( this->m_pString )
        {
            this->>m_LenString = iLen+1;
            strncpy(this->m_pString, szSrc, iLen);
            this->>m_pString[iLen] = '\0';
            this->>m_CountChar = iLen;

            LeaveCriticalSection(&this->m_CriticalSection);
        }
Re[3]: Проблемы при освобождении памяти
От: B0FEE664  
Дата: 22.07.22 12:46
Оценка: +1
Здравствуйте, wbear, Вы писали:

BFE>>Это странно, потому что функция CoTaskMemFree получает указатель на константную строку L"1.4.d". что должно приводить к порче памяти, по идее.

W>После константной строки находиться закомментаренная функция pObj->GetVersionSoft(d); Именно в этой функции я так понимаю какраз выделяется память и записывается строка с версией ПО.
Сигнатуру функции GetVersionSoft можете показать?
Уверены, что память выделяется вызовом CoTaskMemAlloc ?

BFE>>PS В коде много ошибок, ключая утечку памяти и падение при некоторых сценариях использования VString.

W>Согласен что код очень сырой. НО моя задача на текущем этапе в проекте помочь, людям создать из ассемблеровских фрагментов набросок будущей SDK.
W>Думаю что код после меня еще не раз измениться.
Если работу стоит делать, то её стоит делать хорошо.

W>Если укажите возможные сценарии возникновения ошибки — буду благодарен.


VString a("asdf");
const char* p = NULL;
a = p; // утечка


VString a("asdf");
a = a; // падение



А зачем писать свою VString? Возьмите что-нибудь готовое, std::string или CComBSTR ...
И каждый день — без права на ошибку...
Re: Проблемы при освобождении памяти
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 22.07.22 13:10
Оценка:
Здравствуйте, wbear, Вы писали:

W>Который день бьюсь с возникшей проблемой, в части освобождения памяти!

Стандартно для российского форума будут советы не по теме.

W>Для воспроизведения ошибки написал тестовый пример(исходники привел ниже), отличие от рабочего проекта только в закомментаренном вызове функции pObj->GetVersionSoft(d). Ошибка не возникает.

Может так и оставить?

W>Может кто подскажет в чем проблема с освобождение памяти.

Проблема в классе VString. Ненужная критическая секция (да, я читал мотивацию, но она не верная), ненужные ручные управления памятью, для буфера всегда можно использовать std::vector<char>.

W>При условии что другая прога(исходников нет) которая работает с этим COM объектом отрабатывает без ошибок.

Очевидно, там нет VString и сомнительного использования CoTaskMemFree.

W>Исходный код класса VString и тестовый пример ниже.

Это класс который писали вы сами или от евреев пришло? Что-то я не ожидал от них такого . Как сказали выше, лучше использовать std::string и пару функций конверсии без CoTaskMemFree внутри.

W>void WideCharToVString( LPCWSTR lpWideCharStr, VString* a2, int a3)

Это должен быть один из конструкторов VString, но без CoTaskMemFree внутри, SRP и всё такое.
Sic luceat lux!
Re[4]: Проблемы при освобождении памяти
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 22.07.22 13:21
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>А зачем писать свою VString? Возьмите что-нибудь готовое, std::string или CComBSTR ...

Кстати, отличная идея. Есть же ATL с его классами именно для всех этих кейсов.
Sic luceat lux!
Re[3]: Проблемы при освобождении памяти
От: AleksandrN Россия  
Дата: 23.07.22 20:43
Оценка:
Здравствуйте, wbear, Вы писали:

W>Класс VString является частью большой библиотеки по работе с COM серверами и службами. В связи с этим и используется блокировка доступа.


Потоков несколько?
Зачем блокировка доступа в деструкторе? ~VString вызывается из одного потока?
Как объекты VString используются в разных потоках, как сырые указатели?
Re[3]: Проблемы при освобождении памяти
От: AleksandrN Россия  
Дата: 23.07.22 20:55
Оценка:
Здравствуйте, wbear, Вы писали:

W>После константной строки находиться закомментаренная функция pObj->GetVersionSoft(d); Именно в этой функции я так понимаю какраз выделяется память и записывается строка с версией ПО.


В первом сообщении написано, что GetVersionSoft() возвращает LPCWSTR.

В MSDN написано

An LPCWSTR is a 32-bit pointer to a constant string of 16-bit Unicode characters, which MAY be null-terminated.


Передаёт ли GetVersionSoft() владение выделенной памятью вызывающей функции или управление памятью остаётся в pObj?

Какой сценарий использования для этой строки и, созданным из неё, объектом VString?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.