PDA

View Full Version : [C++]Niubbo: Stringhe e malloc


DuHarTapt
18-09-2009, 08:29
Sto provando a capire come funzionano le operazioni su stringhe in C++ e non ho le idee molto chiare, soprattutto sulla deallocazione.


Nel mio programmino ho:



#define STRINGA2 "test"
#define STRINGA1 "mio"
#define ALTRO "prova"

// funziona che concatena le stringhe
char* concatena(char* stringa1)
{
char* ret = (char*) malloc(sizeof(char)*256);
strcpy(ret,stringa1);
strcat(ret,STRINGA2);
return ret;
}


// funzione esterna che ritorna una stringa
char* faAltro(char* st1,char* st2)
{
...

}

// funzione che ne richiama un'altra e dal risultato ottenuto fa un confronto
bool miaFunz(char* ret)
{
char* out = (char*)malloc(sizeof(char)*128);
FunzioneEsterna(NULL,NULL,out);
if(strcmp(out,ret)==0)
return true;
else return false;

}



void MiaClasse::testa()
{
char* ret = (char*) malloc (sizeof(char)* 1024);
ret = faAltro(concatena(STRINGA1),ALTRO);

if(strcmp(ret,"-1")==0) || (miaFunz2(ret)==false))
{
throw miaException("Errore");
}
}



Il tutto funziona, ma:

1- non so se è il modo corretto;
2- dove deallocare la memoria allocata con "malloc"?

Bezelis
18-09-2009, 10:07
Rispondo al secondo punto. Sì, è buona norma liberare la memoria allocata dalla funzione malloc(). Questo perché lo spazio di memoria che hai riservato è persistente, cioè continuerà ad essere riservato fino alla chiusura del programma. Per liberare la memoria dovresti usare la seguente funzione:

void free(void *p);

Quindi appena la memoria non ti serve più, un bel free() e sei a posto. In questo particolare caso, dato che il programma è molto breve e semplice, puoi anche non liberare la memoria (oramai ogni PC ha più di 1 Gb di RAM... più che abbondante, quindi liberare la memoria per così poco è pressocché inutile).

Ricordati di includere la libreria C...

#include<cstdlib>

fero86
18-09-2009, 10:26
il titolo del thread dice tutto: mi pare giusto, i niubbi in C++ allocano le stringhe usando malloc :D

in C++ non si usano malloc e free, si usano new e delete! :doh:
e l'allocazione dello spazio di memoria necessario a tenere una stringa non la si fa manualmente, la si lascia alla classe std::string.

fero86
18-09-2009, 10:34
riscrivo il programma in vero C++ (non l'ho testato peró).


#include <string>
using namespace std;

const char *STRINGA2 = "test";
const char *STRINGA1 = "mio";
const char *ALTRO = "prova";

// funzione che concatena le stringhe
string concatena(const string &stringa1)
{
return stringa1 + STRINGA2;
}


// funzione esterna che ritorna una stringa
string faAltro(string &st1, string &st2)
{
...

}

// funzione che ne richiama un'altra e dal risultato ottenuto fa un confronto
bool miaFunz(const string &ret)
{
string out;
FunzioneEsterna(NULL,NULL,out);
return out == ret;
}



void MiaClasse::testa()
{
string ret = faAltro(concatena(STRINGA1),ALTRO);

if((ret == "-1") || !miaFunz2(ret))
{
throw miaException("Errore");
}
}

Bezelis
18-09-2009, 10:38
in C++ non si usano malloc e free, si usano new e delete!Vero. Però potrebbe voler studiare le due funzioni C, del tutto compatibili col C++ (sempre che includa le librerie appropriate). Se decidesse, per alcuni semplici problemi, di utilizzare la programmazione procedurale piuttosto che quella orientata ad oggetti, niente da dire.

Ho semplicemente risposto alla sua domanda: Dove deallocare la memoria allocata con "malloc"?

DuHarTapt
18-09-2009, 11:00
Rispondo al secondo punto. Sì, è buona norma liberare la memoria allocata dalla funzione malloc(). Questo perché lo spazio di memoria che hai riservato è persistente, cioè continuerà ad essere riservato fino alla chiusura del programma. Per liberare la memoria dovresti usare la seguente funzione:

void free(void *p);

Quindi appena la memoria non ti serve più, un bel free() e sei a posto. In questo particolare caso, dato che il programma è molto breve e semplice, puoi anche non liberare la memoria (oramai ogni PC ha più di 1 Gb di RAM... più che abbondante, quindi liberare la memoria per così poco è pressocché inutile).

Ricordati di includere la libreria C...

#include<cstdlib>




void MiaClasse::testa()
{
char* ret = (char*) malloc (sizeof(char)* 1024);
ret = faAltro(concatena(STRINGA1),ALTRO);

if(strcmp(ret,"-1")==0) || (miaFunz2(ret)==false))
{
throw miaException("Errore");
}
if(ret!=NULL) )free(ret); //-->crash
}



Con questa free ho un crash. Perché?



il titolo del thread dice tutto: mi pare giusto, i niubbi in C++ allocano le stringhe usando malloc :D

in C++ non si usano malloc e free, si usano new e delete! :doh:
e l'allocazione dello spazio di memoria necessario a tenere una stringa non la si fa manualmente, la si lascia alla classe std::string.

Grazie per la tua soluzione. Molto gentile :)
Però poi anche le funzioni esterne accettano char* e non std:string. Devo fare la conversione ogni volta?

Però a questo punto vorrei comunque capire come usare malloc e free :D

fero86
18-09-2009, 12:21
void MiaClasse::testa()
{
char* ret = (char*) malloc (sizeof(char)* 1024);
ret = faAltro(concatena(STRINGA1),ALTRO);

if(strcmp(ret,"-1")==0) || (miaFunz2(ret)==false))
{
throw miaException("Errore");
}
if(ret!=NULL) )free(ret); //-->crash
}



Con questa free ho un crash. Perché? perché il blocco restituito da faAltro evidentemente non é allocato con malloc. quello che hai allocato con malloc alla linea precedente te lo sei perso sovrascrivendo ret col risultato di faAltro.



Grazie per la tua soluzione. Molto gentile :)
Però poi anche le funzioni esterne accettano char* e non std:string. Devo fare la conversione ogni volta? per convertire da string o const string a const char* puoi usare il metodo string::c_str():
string str = "ciao";
const char *sz = str.c_str();

per fare il contrario invece spesso non te ne accorgi neanche perché string ha un costruttore non esplicito che accetta un parametro di tipo const char* e all'occorrenza ha anche un operatore = con stessa segnatura.



Però a questo punto vorrei comunque capire come usare malloc e free :D esattamente come new e delete :asd:

fero86
18-09-2009, 12:24
Se decidesse, per alcuni semplici problemi, di utilizzare la programmazione procedurale piuttosto che quella orientata ad oggetti, niente da dire. non vedo alcun nesso tra il paradigma di programmazione da adottare e le primitive di allocazione di memoria dinamica da usare: in C++ si puó benissimo programmare in maniera procedurale usando new e delete.


Ho semplicemente risposto alla sua domanda: Dove deallocare la memoria allocata con "malloc"? e chi ti ha detto niente :D

fero86
18-09-2009, 12:28
mi correggo: esattamente come new e delete :asd: a parte naturalmente la sintassi diversa visto che malloc e free sono due funzioni mentre new e delete sono due operatori, in realtá c'é una differenza semantica importante tra malloc e new: in caso di risorse di sistema insufficienti malloc restituisce NULL, mentre il comportamento di new é configurabile ma di default viene lanciata un'eccezione che adesso non ricordo qual é; la conseguenza dell'eccezione é il crash del programma a meno che non la si catturi e la si gestisca. io in genere preferisco sempre mettermi l'anima in pace e lasciare andare qualunque potenziale eccezione lanciata da new: la mia politica di programmazione é che se il sistema é talmente intasato da non potermi allocare la mia memoria tanto vale che il mio programma si chiuda, cosi libero anche un po' di risorse al sistema.

DuHarTapt
18-09-2009, 13:12
perché il blocco restituito da faAltro evidentemente non é allocato con malloc. quello che hai allocato con malloc alla linea precedente te lo sei perso sovrascrivendo ret col risultato di faAltro.


Ho recuperato il codice sorgente di faAltro() che è:



char* faAltro(char* a,char* b)
{
char* a1 = (char*) malloc(sizeof(char)*256);
(//...codice)
int size=strlen(b);
return funcB(a1,size);
}



char* funcB(char* a, int size)
{
static char* aaa = "provaprova";
if (size <= 0)
return "";
char * ret = (char*) malloc(sizeof(char)*size+1);

for (int i = 0; i<size; i++)
{
ret[i] = 'a'; // in realtà fa altro, ma non è importante
}
ret[size]='\0';
return ret;
}



quindi la malloc viene fatta in una funzione richiamata da faAltro, e, non in faAltro direttamente
Cambia qualcosa?

fero86
18-09-2009, 15:59
senti, facciamo una cosa? :asd:
posta il sorgente completo e disinfestato da qualunque possibile codice di prova; dopodiché vediamo perché crasha :)

PS: "sizeof(char)*size+1" non ha senso, primo perché sizeof restituisce la dimensione di un certo tipo espressa in char (quindi fa sempre 1) e secondo perché non ha senso calcolare lo spazio di size caratteri piu un byte, semmai dovrebbero essere (size+1) caratteri.

Ikon O'Cluster
19-09-2009, 01:05
dato che il programma è molto breve e semplice, puoi anche non liberare la memoria

E' appena morto Strustrup e si è portato il C nella tomba... Diamoci tutti allegramente al JAVA! :D

la mia politica di programmazione é che se il sistema é talmente intasato da non potermi allocare la mia memoria tanto vale che il mio programma si chiuda, cosi libero anche un po' di risorse al sistema.

BRIVIDOOOO!!! No non va per niente bene! Almeno fai scrivere un messaggio... non puoi fare un'uscita così selvaggia. Quello sventurato che si ritrova il crash come minimo penserà (ignorante com'è) di aver distrutto il PC e che tra poco verrà licenziato! Se muore di infarto te lo porti sulla coscienza... :D E cmq la tua politica non è furba: a volte è sufficiente interrompere l'operazione e chiedere di riprovare nuovamente, semmai questo evita di perdere il lavoro eventualmente si può invitare l'utente a chiudere altre applicazioni... Mi sa che forse forse puoi fare di meglio! ;)

Ikon O'Cluster
19-09-2009, 01:40
Però a questo punto vorrei comunque capire come usare malloc e free

MALLOC
void *malloc(size_t size);
size è il numero di bytes che si tenta di allocare.
RISULTATO un puntatore al buffer di memoria allocato. E' NULL se size è uguale a zero. E' NULL con size diverso da zero se non è stato possibile allocare size bytes.

void *calloc(size_t nmemb, size_t size);
nmemb numero di elementi da allocare (ogni elemento ha dimensione pari a size).
size è il numero di bytes che si tenta di allocare per ogni elemento.
RISULTATO un puntatore al buffer di memoria allocato. E' NULL se size o nmemb è uguale a zero. E' NULL con size ed nmemb diversi da zero se non è stato possibile allocare nmemb*size bytes.

void free(void *ptr);
ptr puntatore ad un buffer di memoria da deallocare. Il buffer deve essere stato creato con malloc oppure calloc. Se NULL nessuna operazione viene eseguita.

------------------------------------

Non so qual è il tuo grado di preparazione sul C, non mi sembra troppo approfondito, quindi cercherò di essere completo.

void* è un "generico" puntatore: punta ad un'area di memoria la quale non si sa bene come interpretarla. Ad esempio:

void* numero_intero = malloc(4);

Quei 4 byte allocati il compilatore non sa che roba sono, può essere un int, ma possono essere anche 4 char messi a formare un array. Insomma il void* essendo generico va gestito con un "cast":

int* intero = (int*)malloc(sizeof(int));
*intero = 100;

In questo modo il risultato della malloc viene cast-ato in un int*.

Adesso ho un puntatore ad un'area di memoria. Ad esempio ho un foglietto con sopra scritto "LIBRO x PAG y" riuscirò sempre a trovare l'informazione che voglio. Se però tu mi fai:

int altro_intero = 5;
intero = &altro_intero;

Cosa hai fatto? Hai buttato il "bigliettino" e scritto un altro che adesso punta a "LIBRO x PAG z", ovvero punta ad un'altra zona di memoria. E la zona di memoria di prima che fine fa? Sta là che aspetta di essere usata ma tu non la userai mai perchè hai perso l'unico "bigliettino" che ti permetteva di raggiungerla. Ancora peggio non potrai nemmeno deallocare quell'area di memoria che resterà lì inutilizzata per tutta l'esecuzione del programma. Queste aree perdute (non più raggiungibili) sono dette "garbage". In JAVA non è un problema perchè esiste un "programma" apposito (garbage collector) che periodicamente controlla e dealloca queste aree di memoria perduta. In C/C++ è una delle cose peggiori che puoi fare...

Veniamo alla free. La free la devi fare sempre. Tu considera che se hai N malloc/calloc dovrai avere anche N free. Attenzione quando passi l'argomento alla free. Se gli passi un puntatore ad un'area di memoria allocata con malloc/calloc tutto va bene. Se gli passi NULL va bene uguale perchè tanto non fa niente. Ma se fai una cosa del genere che succede?

int* intero;
free(puntatore);

In questo caso intero non essendo inizializzato contiene un valore "casuale". Pertanto punta da qualche parte in memoria chissà dove. Ci sono varie possibilità:

1) Hai fortuna vale NULL... tutto OK;
2) Meno fortuna hai azzeccato il punto in cui tieni memorizzato il tuo lavoro di una vita e sta per essere deallocato all'istante;
3) Punta ad un'area riservata/non allocata e il programma ti crasha con il più odiato dei "segmentation faul". Adesso ti toccherà cercare il punto in cui è crashato tra le tue 200.000 righe di codice... tanti auguri!

Un altro caso è:

int* intero = (int*)malloc(sizeof(int));
*intero = 100;
free(intero);
free(intero);

Alla seconda free vai a deallocare una zona già deallocata quindi ti riconduci dritto dritto al precedente punto 3!!!

fero86
20-09-2009, 01:33
Almeno fai scrivere un messaggio... a questo ci pensa il runtime del mio compilatore. forse in Visual C++ é anche possibile implementare un hook per eccezioni non gestite che richieda al sistema di generare il crash dump cosi che l'utente lo possa inviare agli sviluppatori, dovrei documentarmi.


Quello sventurato che si ritrova il crash come minimo penserà (ignorante com'è) di aver distrutto il PC e che tra poco verrà licenziato! ci sono due buoni motivi per non farne una questione di interazione uomo-macchina:
1) si tratta di un'eventualitá rarissima;
2) molto prima ancora é una questione di ingegneria del software: il non dover controllare ogni singola allocazione dinamica semplifica considerevolmente il codice.


E cmq la tua politica non è furba: a volte è sufficiente interrompere l'operazione e chiedere di riprovare nuovamente, é questo che non é furbo: cosi il codice tipicamente diventa 10 volte piu complicato. direi che la definizione di furbizia in questo caso va esattamente a mio vantaggio: accettando le tue argomentazioni infatti si potrebbe concludere che ottengo un risultato peggiore allo scopo di fare economia di codice e quindi di lavorare di meno :Prrr:


semmai questo evita di perdere il lavoro se il software é scritto bene non lo si perde comunque: un'eccezione C++ comunque non provoca la chiusura immediata, prima c'é lo stack unwinding.


eventualmente si può invitare l'utente a chiudere altre applicazioni... suggerimento inutile visto che le condizioni di cui stiamo parlando possono verificarsi anche se in esecuzione c'é solamente il tuo programma. pensa ad esempio a questo programma:

int main()
{
while (true)
{
new int[0x1000];
}
}
é chiaro che la cosa non potrá andare avanti per molto... :D


Mi sa che forse forse puoi fare di meglio! ;) mi sa tanto invece che io posso fare quel picchio che mi pare, indipendentemente dalla bontá dei tuoi inutilissimi giudizi :rolleyes:


PS: vuoi provare il vero brivido? eccotelo: in Java il fallimento di un'allocazione di memoria provoca un'eccezione come in C++, anzi no, peggio, provoca un errore: http://java.sun.com/javase/6/docs/api/index.html?java/lang/OutOfMemoryError.html

solo che a differenza del C++ in Java la cosa non é configurabile: quando programmi in Java puoi farlo con la consapevolezza che in moltissimi punti dell'esecuzione il tuo programma potrebbe crashare per motivi al di fuori del tuo controllo e tu non sei nemmeno in grado di salvare il lavoro dell'utente (cosa che in C++ invece puoi fare benissimo all'interno di un distruttore). o meglio, in teoria potresti circondare con blocchi try-finally ogni singolo uso dell'operatore new ed ogni singola chiamata a metodi esterni al tuo codice (compresi quelli della libreria standard) visto che non sai chi di loro chiama new, ma tu lo fai? :)
no che non lo fai, e fai bene a non farlo perché é irragionevole. allora potresti mettere un try-finally intorno all'entry point di ogni tuo thread, quindi nel metodo main e in ogni metodo run di Runnable o di Thread; e se l'errore salta mentre stai nell'EDT di Swing? o, analogamente, nel contesto di un thread creato da codice esterno?