[C++] Dylemat z pamięcią

0

Mam dylemat, bo moja wiedza co do pamięci alokowanej dynamicznie jest trochę ograniczona i zastanawiam się nad dwoma sposobami zwolnienia jej w pewnym przykładzie (Są to dwie metody relizujące wspólnie konwersję łańcuchów znakowych z wchar_t* do char*). Który sposób jest bardziej poprawny i nie spowoduje wycieku?? (Myślałem też o inteligentnych wskaźnikach, ale na razie chcę to rozwiązać w ten sposób ;) ) [Wybaczcie wstawki z angielskiego ;] ]

I sposób:

char* km_Common::getSingleString(const wchar_t* wString) throw()
{
    char* retString;

    try {
      retString = protGetSingleString(wString);
    } catch (const km_ErrorRuntime& e) {
      //todo: inform about an error here
      //MessageBoxA(NULL, e.what(), "Error", MB_OK);
      return "";
    }

    return retString;
}

char* km_Common::protGetSingleString(const wchar_t* wString) throw(km_ErrorStringConvert)
{
    //! Obtain size of the string, MSDN ::WideCharToMultiByte
    int iSize = ::WideCharToMultiByte(CP_ACP, 0, wString, static_cast<int>(wcslen(wString)), NULL, 0, NULL, NULL);
    if (iSize <= 0)
    {
        //throw exception
        throw km_ErrorStringConvert("Error while converting from wchar_t* to char*. Unknown size!");
    }

    char* cString;
    try {
      cString = new char [iSize];
    } catch (const std::bad_alloc &e) {
      //memory couldnt be allocated
      return "";
    }

    //! Convert the string
    int ret = ::WideCharToMultiByte(CP_ACP, 0, wString, static_cast<int>(wcslen(wString)), cString, iSize, NULL, NULL);
    if (ret == 0)
    {
        //throw exception
        delete[] cString;
        throw km_ErrorStringConvert("Error while converting from wchar_t* to char*. Operation failed!");
    }

    return cString; // czy tu nastąpi wyciek? wskaźnik ze stosu jest niszczony, ale co ze stertą?
}

II sposób:

char* km_Common::getSingleString(const wchar_t* wString) throw()
{
    char* retString;
    char* cString = NULL;

    try {
      retString = protGetSingleString(wString, cString);
    } catch (const km_ErrorRuntime& e) {
      //todo: inform about an error here
      //MessageBoxA(NULL, e.what(), "Error", MB_OK);
      delete[] cString; //zwolnienie pamieci zaalokowanej w protGetSingleString(), tylko co sie stanie w momencie wyrzucenia w protGetSingleString wyjątku std::bad_alloc ???
      return "";
    }

    delete[] cString; // j/w
    return retString;
}

char* km_Common::protGetSingleString(const wchar_t* wString, char* cString) throw(km_ErrorStringConvert)
{
    //! Obtain size of the string
    int iSize = ::WideCharToMultiByte(CP_ACP, 0, wString, static_cast<int>(wcslen(wString)), NULL, 0, NULL, NULL);
    if (iSize <= 0)
    {
        //throw exception
        throw km_ErrorStringConvert("Error while converting from wchar_t* to char*. Unknown size!");
    }

    try {
      cString = new char [iSize];
    } catch (const std::bad_alloc &e) {
      //memory couldnt be allocated
      return "";
    }

    //! Convert the string
    int ret = ::WideCharToMultiByte(CP_ACP, 0, wString, static_cast<int>(wcslen(wString)), cString, iSize, NULL, NULL);
    if (ret == 0)
    {
        //throw exception
        delete[] cString;
        throw km_ErrorStringConvert("Error while converting from wchar_t* to char*. Operation failed!");
    }

    return cString;
}

Z góry dzięki za pomoc ;)

P.S. Oba przykłady kompilują się i "niby" działają poprawnie.

0

Nie wgłębiałem się w kod, ale zamiast babrania się z alokacją użyj klasy string. Po co się męczyć zwłaszcza skoro "moja wiedza co do pamięci alokowanej dynamicznie jest trochę ograniczona" W nowszych językach programista w ogóle nie musi jawnie nic alokować.

#include <windows.h>
#include <iostream>
#include <string>

using namespace std;

typedef basic_string<wchar_t> ustring;

string UstrToAstr(ustring ustr)
{
   int size = ustr.size();
   string result;
   result.resize(size);
   wcstombs(&result[0], &ustr[0], size);
   return result;
}
0

Teoretycznie mógłbym tak zrobić, ale chodzi tutaj raczej o poszerzenie swojej wiedzy ;)

0

Ok. A więc przy alokacji dynamicznej jest taka zasada, że kto alokuje pamięć ten ją zwalnia. Nie możesz w funkcji alokować pamięci i zwracać do niej wskaźnik i na tym koniec, pozostawiając kwestię dealokacji odbiorcy nowego łańcucha.
Możesz podać funkcji konwertującej 2 już zaalokowane bufory, tak jak to robi mbstowcs, albo skorzystać z obiektów zarządzających pamięcią jak string czy inteligentny wskaźnik.

Wg mnie dla ciebie najlepsze rozwiązanie to klasa string.

A jak chcesz poćwiczyć alokację - napisz jakąś dynamiczną strukturę danych jak dynamiczna tablica (np. własny std::string) albo jakieś drzewko.

0

Dobra, przerobie tą część tak, żeby oprzeć się na std::string.

Dzięki za pomoc. :)

0

Drobna poprawka kodu adf88.

string UstrToAstr(const wstring &ustr)
{
	size_t size = wcstombs(NULL,ustr.c_str(),ustr.size());
	if(size==-1)return "";
	
	string result(size,'\0');
	if(wcstombs(&result[0], ustr.c_str(),size)==-1)result.clear();
	return result;
}
0
kolomati napisał(a)

I sposób:

char* km_Common::getSingleString(const wchar_t* wString) throw()
{
    char* retString;

    try {
      retString = protGetSingleString(wString);
    } catch (const km_ErrorRuntime& e) {
      //todo: inform about an error here
      //MessageBoxA(NULL, e.what(), "Error", MB_OK);
      return "";
    }

    return retString;
}

tragedia.
return "";
kontra
retString = protGetSingleString(wString);
return retString;
jesli procedura X uzyje metody getSingleString i dostanie w efekcie wskaznik, to skad bedzie wiedziala, czy ma go potem zwolnic czy to moze jest const chsr const* == "" hardcodeowany w kodzie?? proba zwolnienia "" da pieknego crasha calej aplikacji, niezwalnianie -- da wycieki pamieci. zwalnianie gdy !="", da wycieki pamieci, bo przeciez oryginalny wchar_t* mogl zawierac tylko ""..

char* km_Common::protGetSingleString(const wchar_t* wString) throw(km_ErrorStringConvert)
{
    char* cString;
    try {
      cString = new char [iSize];
    } catch (const std::bad_alloc &e) {
      //memory couldnt be allocated
      return "";
    }

ta sama uwaga co powyzej. caller nie bedzie wiedzial czy dostanie cos co wymaga zwolnienia, czy cos co nie ma prawa byc zwolnione

    //! Convert the string
    int ret = ::WideCharToMultiByte(CP_ACP, 0, wString, static_cast<int>(wcslen(wString)), cString, iSize, NULL, NULL);
    if (ret == 0)
    {
        //throw exception
        delete[] cString;
        throw km_ErrorStringConvert("Error while converting from wchar_t* to char*. Operation failed!");
    }

    return cString; // czy tu nastąpi wyciek? wskaźnik ze stosu jest niszczony, ale co ze stertą?
}

przed throw'em zwalniasz cString, wiec jak ramka funkcji bedzie niszczona za sprawa rzucanego wyjatku, to sterta tez bedzie zwolniona. natomiast jak ie bedzie wyjatku -- ramka stosu bedzie zniszczona, wskaznik zostanie skopiowany do ramki "powyzej", a sterta bedzie nienaruszona. wszytko 100% prawidlowo tutaj

II sposób:

char* km_Common::getSingleString(const wchar_t* wString) throw()
{
    char* retString;
    char* cString = NULL;

    try {
      retString = protGetSingleString(wString, cString);
    } catch (const km_ErrorRuntime& e) {
      //todo: inform about an error here
      //MessageBoxA(NULL, e.what(), "Error", MB_OK);
      delete[] cString; //zwolnienie pamieci zaalokowanej w protGetSingleString(), tylko co sie stanie w momencie wyrzucenia w protGetSingleString wyjątku std::bad_alloc ???

na cholere ten delete[] ? przeciez tutaj cString jest NULLem (protGet pobiera cString* przez wartosc.. wskaznik nie zmieni sie na zewnatrz!).. zreszta, jak protGetSingleString rzuci wyjatkiem, to nic nie zwroci i retString nie zostanie ustawiony na nic..

      return "";
    }

    delete[] cString; // j/w
    return retString;
}

primo: na chorobe Ci cString, przeciez ta zmienna jest ZAWSZE ROWNA NULL w tej funkcji..
secundo: ta sama uwaga co do metody I -- zwracanie "" jest niebezpieczne..

char* km_Common::protGetSingleString(const wchar_t* wString, char* cString) throw(km_ErrorStringConvert)
...

te same uwagi jak do metody I

1 użytkowników online, w tym zalogowanych: 0, gości: 1