Newsgroup: it.comp.lang.c++
Titolo: Domanda su stringhe

Se creo una funzione del tipo : char *function(...)   nel momento in
cui la uso , se per esempio mi deve dare una stringa ,  devo prima
creare una stringa o con new o definendola , perche se provo ad
allocare della memoria dentro la stessa function poi quando esco
ovviamente tale memoria viene liberata.
Me poi sono incappato in una funzione di Borland Builder che converte
una AnsiString in un char[].Tale funzione e' Stringa->c_str().
Ma tale funzione restituisce un puntatore a una stringa che non deve
essere nacessariamente definita o allocata prima.
Mi spiego meglio : non c'e' bisogno che faccio :
char str[10];
str=String->c_str();
cout<<str;
ma posso direttamente fare :
cout<<String->c_str();
Come posso fare anch'io questa cosa con le mie funzioni ???
 

Come posso fare anch'io questa cosa con le mie funzioni ???
All'interno della tua funzione ti mallochi (o new-i, visto che si parla di
c++) lo spazio per la stringa, e ritorni il puntatore.
Quando non ti serve piu' devi freearla (o deletarla).
Marco

Quando non ti serve piu' devi freearla (o deletarla).
Pessima pratica, meglio passare un reference ad un buffer
fornito dal chiamante.
Paolo
--------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
--------------------------------------------------

fornito dal chiamante.
Leggi la richiesta del postante.
Marco Fabiani

 fornito dal chiamante.
In C, forse, ma e` una prassi che ha i suoi problemi (bisogna
permettere al chiamante di sapere quanto spazio gli serve, e
il chiamante si fa ogni volta il mazzo duplice di scoprire lo
spazio, allocarlo, poi chiamare la funzione che da` le info).
Programmando le nude API di Windows, e` un idioma che si
incontra ad ogni pie` sospinto, e ha i suoi costi...!
In C++, per fortuna, esistono impostazioni enormemente
migliori.  Invece di pensare a "una mia funzione", si pensa
ad "un mio OGGETTO", che contiene sia "la funzione", sia
i *dati* stessi -- sotto forma di std::string in questo caso,
di std::vector in altri casi analoghi.
Pensare che usare C++ invece di C significhi usare new
invece di malloc significa sfruttare ben poco del C++...!!!-)
Alex


e no .... se faccio cosi la memoria viene liberata automaticamente nel
momento in cui va fuori scopo ,cioe' quando la funzione ritorna.
Esempio :
char *Function()
{
char stringa[]="asdfghty";
return stringa;
}
Questa non va mica bene ..... viene si accettata in fase di
compilazione ma provoca un run-time error .... ovviamente.
Invece la mia domanda e' :
come fare ad allocare della memoria DENTRO la funzione , e non
permettere che venga liberata automaticamente nel momento in cui va
fuori scopo ?
Perche' e' questo che fa' la c_str() vero ?
Grazie.

No, c_str() e' una funzione membro della classe AnsiString, non una
funzione libera. E' la classe AnsiString che possiede e mantiene un
membro dato di tipo char * ed e' questo che c_str() ritorna.
Ad esempio (ne compilato ne testato, giusto per dare l'idea):
class AnsiString {
  public:
    AnsiString() : str(0) {}
    AnsiString(char * s) { str = new[] char(strlen(s) + 1);
strcpy(str,s); }
    ~AnsiString() { delete str; }
    char * c_str() { return str; }
  private:
    char * str;
};
Nota che non e' detto che AnsiString sia fatta cosi', questo e' solo un
esempio di come potrebbe funzionare. Tra parentesi la classe AnsiString
e' un'invenzione Borland; la classe standard si chiama string, e
rispetto a quanto descritto sopra si comporta nello stesso modo.
Ciao,
Nicola

}
Infatti non l'hai mallocata.
Prova con
char    *f()
{
char    *p;
    p = (char *)malloc(10);
    strcpy(p, "prova");
    return p;
}
Marco Fabiani


Non va' , e' lo stesso.La memoria puntata da p viene liberata nel
momento in cui la funzione ritorna : la memoria che allochi con malloc
va fuori scopo quando la funzione termina.


Hai ragione non ci avevo proprio pensato.
... quindi questa cosa , cioe' di poter far si che una funzione passi
un puntatore ad una stringa (o un vettore in generale) che non viene
deallocata automaticamente, e quindi toglie la necessita' di usare un
buffer esterno alla funzione , e' possibile in c++ e non in c , giusto
?
Grazie.

Non e' del tutto vero. In C puoi ottenere l'effetto che desideri
utilizzando una variabile locale statica all'interno della tua funzione.
Ad esempio:
char * conservaStringa(char * s) {
  static char * p = 0;
  if ( s != 0 ) {
    delete p;
    p = (char *)malloc(strlen(s)+1);
    strcpy(p,s);
  }
  return p;
A meno di errori (anche questa non e' ne compilata ne testata) questa
funzione restituisce sempre la stessa stringa se le passi NULL come
argomento e sostituisce la stringa se passi un argomento non nullo.
Ciao,
Nicola

va fuori scopo quando la funzione termina.
Ma quando mai?
Marco Fabiani

Marco Fabiani wrote:

La memoria allocata da malloc non e' liberata quando si esce dallo scope
della funzione; purtroppo viene liberato il puntatore p, che occupa una
zona di memoria di nuovo a disposizione del processo per essere utilizzata,
magari 20 minuti piu' tardi...Quindi la memoria puntata da p e' protetta,
la memoria occupata da p no. Quindi
ritornare il valore del puntatore non dovrebbe dare problemi; ritoranare il
puntatore al
puntatore da' problemi.
Nell'esempio viene ritornata una variabile char* che e' la copia di p,
quindi non ci sono
problemi e dovrebbe funzionare...la prima volta! Cosa succede se si chiama
la funzione f() una seconda volta? Succede che viene RIallocato un p,
RIcopiato in uscita e RIliberato un p. Ogni volta che f() viene chiamata
crea un nuovo pezzettino di
memoria allocata. Questo puo' essere esattamente quanto voluto (f() diventa
una
specie di wrapper per malloc....), oppure no. In particolare, un problema
potrebbe
essere: se f() alloca, chi disalloca?
Ci sono delle ovvie differenze tra usare una stringa statica invece che
malloc: prima di tutto la stringa statica alloca sempre la stessa quantita'
di memoria, mentre con malloc
puoi deallocare e allocare quanto vuoi; secondo, la stringa statica viene
allocata alla prima volta che il flusso del programma passa per essa,
mentre con malloc puoi deallocare e allocare quando vuoi; terzo, la stringa
statica non puo essere allocata con
un valore non noto al compile-time, mentre con malloca puoi allocare e
deallocare quello che vuoi.
In definitiva, malloc da' maggiore potere, e anche maggiori
responsabilita'.
P.S. tutto cio' e veramente programmazione in C, non in C++

 P.S. tutto cio' e veramente programmazione in C, non in C++
Infatti.  std::string, std::vector, ecc, rendono tutte queste
problematiche piu` o meno obsolete in C++.
E` allucinante che ci sia chi si ostini a difendere l'uso di
questi arcaismi, quando il linguaggio C++ standard offre,
nelle sue standard librerie, soluzioni preferibili nel 99+%
dei casi.  Quanta frazione di questo gruppo, e quanta
del tempo e dell'energia dei programmatori che "dicono"
di usare C++, e` sprecata a discutere di questioni che
non dovrebbero neppure sorgere...?-)
Alex

In C, forse, ma e` una prassi che ha i suoi problemi
[omissis]
Mi aspettavo questa replica... :)
Sono perfettamente d'accordo, la mia critica era relativa ad
un confronto tra allocare un oggetto nella funzione e
restituirne un puntatore all'esterno e appunto delegare
al chiamante il compito di fornire il buffer... senza considerare
altre possibilita`.
Non a caso ho detto "meglio" e non "il modo migliore"!
In C++ esistono soluzioni ancora migliori, usare appunto un
contenitore standard oppure una classe che incapsuli
le funzionalita` necessarie sulla falsa riga dei vari std::vector
std::list ecc. ecc.


Leggi la richiesta del postante.
Se una cosa non funziona, non bisogna pensare di metterci una pezza
sopra... bisogna cambiare la cosa. Una impostazione errata con un pezza
sopra continuera` ancora a causare mille altri problemi.


essere: se f() alloca, chi disalloca?
Che e' esattamente cio' che chiedeva il poster originale.
Infatti ho segnalato di ricordare di deallocare la stringa quando non serve
piu'.
Marco Fabiani

sopra continuera` ancora a causare mille altri problemi.
Va bene, ragazzi.
La richiesta del postante era questa.
Se avete voglia (e tempo) di suggerirgli i migliori metodi per evitare
quella necessita' ben venga, cosi' imparo pure qualcosa.
Altrimenti tali discussioni, tra l'altro sterili, non servono a me (non mi
interessa molto il c++, a meno di quel poco che mi serve), non servono a te
(non hai sicuramente bisogno di "ripassare" il c++) e non servono nemmeno al
postante (aveva un semplice problema, non doveva mandare in orbita lo
shuttle).
Marco Fabiani

Si presume che interessino "soltanto" tutti gli altri partecipanti al
gruppo, che non a caso si chiama it.comp.lang.c++ (l'accento va sul
"++").
Ciao,
Nicola

 va fuori scopo quando la funzione termina.
Attento, non č cosě. Rileggiti il capitolo sui puntatori e
sull'allocazione dinamica.
EMG

Marco Fabiani wrote:

Beh, veramente il post originale voleva fare qualcosa come
cout<<String->c_str();
e quindi non voleva che l'utente si preoccupasse di allocare e deallocare.
Inoltre, mi sembra di capire che non volesse cambiare il "contenuto", ma solo
accederci per leggerlo ed eventualmente copiarlo.
Inoltre non voleva una classe, ma una funzione (globale).
Visto che non aveva ulteriori requirements, restano altre cose non specificate:
1) vuole che la funzione ritorni sempre la stessa stringa, o vuole che, in
funzione del comportamento run-time, ritorni una stringa differente, magari
differente all'interno dello stesso run o differente tra un run e l'altro?
2) vuole che tutte le variabili inizializzate con il puntatore ritornato dalla
funzione siano dipendenti dall'evoluzione della funzione stessa, o che siano
indipendenti?
Secondo i requirements, le soluzioni sono molto diverse. Quasi sicuramente
pero'
si puo' anticipare che saranno per un verso o per l'altro PERICOLOSE, e che
richiederanno la comprensione di quanto si sta facendo.

 Alex

accederci per leggerlo ed eventualmente copiarlo.
In tal caso sarebbe valida (se funge, non l'ho provata) la soluzione di
Musatti.
Marco Fabiani

La richiesta del postante era questa.
Dal mio punto di vista una persona che fa una tale richiesta
non ha molta pratica di C++. In questo non c'e` nulla di male, il
male sta nel vedere un suggerimento non del tutto valido e nel non
far notare la cosa.
Dal mio punto di vista e` preferibile evitare di suggerire
"abitudini" e/o "soluzioni" tutt'altro che apprezzabili. Meglio
fargli capire che forse dovrebbe fermarsi un po' di piu` a
riflettere su cosa vuole fare e considerare che probabilmente
ci sono soluzioni migliori.
Nello specifico, allocare memoria in una funzione e affidare al
chiamante il compito di deallocarla e` una pessima pratica
perche` (come evidente dal post successivo) il chiamante puo`
non aver chiaro (e/o ricordare) che deve poi deallocare.
Suggerire di passare un buffer di cui il responsabile e` solo e
unicamente il chiamante non mi pare una sterile discussione, ma
un invito a riflettere su chi e` meglio abbia certe responsabilita` e
sulla importanza di renderle chiare.
D'altronde lo scopo di questo NG non e` semplicemente
suggerire pezze!


 niente di male a cercare di capire come stanno le cose.
Se fosse questa la vera motivazione, come spiegheresti la
scarsita` di domande e risposte analoghe su sin ecc ecc?-)
Cavilla pure, la realta` non cambia...
Alex

 sulla importanza di renderle chiare.
La chiarezza e` fondamentale, e siamo in una discussione
tutt'altro che sterile (in astratto, poiche` in concreto non
c'e`, nel 99+% dei casi, alcuna ragione di evitare l'uso dei
contenitori standard!).  Ma sul punto specifico, la disciplina
che tu suggerisci richiede sistematicamente due "round
trips" fra i sottosistemi chiamante e chiamato (uno per
scoprire quanto dev'essere grande il buffer, un altro per
passare il buffer cosi` dimensionato), e si presta altrettanto
bene ad errori come la disciplina "chiamato alloca, chiamante
libera" -- per quest'ultima, basta d'altronde ritornare un
std::auto_ptr<T> invece di un T* (almeno sinche` parliamo
di scalari; l'uso di _array_ dinamicamente allocati e` sempre
piu` problematico, e giustamente lo standard, per scoraggiarli,
NON prevede un equivalente per array di std::auto_ptr...!) e
il grosso dei problemi spariscono, non ti pare?
Alex

 std::list (idem con patate)...?
Hai dimenticato uno dei motivi: i docenti danno cose di questo genere
come compiti...  poi c'e' chi vuol far fare il compito al newsgroup, e
c'e' chi perde l'abitudine alla standard library (o non l'acquista;
quanti usano <algorithm>, <functional>, <numeric> e via dicendo?).
-- 
Maurizio Loreti                         
http://www.pd.infn.it/~loreti/mlo.html
Univ. of Padova, Dept. of Physics - Padova, Italy            
lor...@pd.infn.it

dinamica della memoria invece delle librerie standard,
Come (penso di avere) sottolineato in un precedente post, la
mia risposta prescindeva dall'uso dei contenitori standard.
Comunque concordo sul fatto che nel 99+% dei casi non
c'e` motivo per rinunciare ai contenitori standard .


venga finalmente superata.
Penso che bastera` molto di meno... diciamo un 5 anni?



Vero . Mi ero confuso con il discorso sui distruttori di oggetti.
Il problema e' che ho cominciato a programmare in c++ solo qualche
mese fa- E ho studiato un mattone di 960 pagine di un certo Herbert
Schildt .... in una settimana . Mi era venuta la voglia di fare degli
algoritmi genetici . Be' non sono certo un profondo conoscitore di c++
e neanche mi interessa , quello che mi interessa e' mettere in pratica
le idee che mi vengono. Infatti da allora ho fatto sia dei discreti
algoritmi genetici che una rete neurale che interpreta i caratteri
dell'alfabeto scritti manualmente. Man mano che trovo degli ostacoli
legati alla programmazione vado a rivedermi il famoso mattone.
Il mio bisogno , non impellente , di avere una funzione che
restituisca una stringa era solo per comodita'. In effetti il fatto di
dover liberare la memoria allocata dalla funzione fa pendere la
bilancia dalla parte della soluzione del buffer.
Oppure , soluzione secondo me + elegante , usare un oggetto.Ma in
alcuni casi mi sembra superfluo.
Grazie per le risposte.

 c'e` motivo per rinunciare ai contenitori standard .
Su questo punto siamo pienamente d'accordo.
Non lo siamo sul problema (molto meno importante) del
migliore idioma da usare per quel meno-dell'1%-dei-casi
in cui l'allocazione dinamica e` effettivamente da usare.
Tu (se ho ben capito: correggimi se sbaglio) preferisci
l'idioma in cui si hanno *due* funzioni separate, tipo:
    size_t XYZSpazioNecessario(argomenti);
    void XYZCalcolaDavvero(argomenti, void* buffer, size_t spazio);
(o magari le sue piu` confuse varianti in cui le due funzioni
vengono aggregate in una sola, che fa diversi lavori a seconda
degli argomenti ecc?)
Il codice-cliente sarebbe dunque del tipo:
    size_t spazio = XYZSpazioNecessario(argomenti);
    void* buffer = ::operator new(spazio);
    XYZCalcolaDavvero(argomenti, buffer, spazio);
    // usa i dati nel buffer ritornato
    ::operator delete(buffer);
Giusto?
Io preferisco idiomi alternativi, dove l'allocazione (e se del
caso anche il rilascio) sono lasciati al sottosistema chiamato,
quello che sa quanto spazio sia necessario.  Ad esempio:
    void XYZAllocaECalcola(argomenti, void** buffer);
    void XYZLiberaBuffer(void* buffer)
con codice-cliente del tipo:
    void* buffer;
    XYZAllocaECalcola(argomenti, &buffer);
    // usa i dati nel buffer ritornato
    XYZLiberaBuffer(buffer);
La maggiore semplicita` del mio idioma preferito, dal punto
di vista del codice-cliente, mi sembra indiscutibile.  Non mi
e` chiaro perche` tu ritenga piu` prono all'errore (dimenticanza
di chiamata di XYZLiberaBuffer) questo "mio" idioma, rispetto
a quello precedente (dimenticanza di chiamata di ::operator
delete); il codice-cliente del "tuo" idioma deve fare piu` cose,
e garantire piu` invarianti, quindi logica suggerisce che sia
PIU`, non MENO, prono ad errori.
Inoltre, le cose possono in realta` essere piu` semplici con
il "mio" idioma anche dal lato dell'implementazione (codice
servente).  Ad esempio, non e` raro che la quantita` di
lavoro necessaria per stabilire "la quantita` di spazio che
occorre" sia equivalente a quella per "generare i risultati"
(situazioni di tipo "stream", con risultati generati via via);
in questo caso, il "tuo" idioma (per evitare disastrosi crolli
di prestazioni) costringe il codice-servente a fare salti
mortali con salvataggi dei risultati in buffer temporanei
(che sopravvivano alla richiesta di lunghezza-necessaria
e siano pronti per essere copiati alla richiesta di fare
davvero i calcoli) oltre naturalmente alla "copia in piu`"
che puo` comunque rendersi necessaria; lo stesso puo`
dirsi, del tutto a parte le prestazioni, per quei numerosi
casi in cui il processo di ottenere i dati, oltre ad essere
l'unico modo di misurare la loro lunghezza, e` anche
irreversibile (ad esempio, lettura da una pipe o socket).
Si potrebbe continuare, ma il punto e` che vedo solo
svantaggi, e nessun vantaggio, per il "tuo" idioma rispetto
al "mio"; e vorrei quindi che tu chiarissi cosa te lo fa
preferire.
Alex

you wrote:
char * conservaStringa(char * s) 
{
       static char * p ;
 
       if ( s != 0 ) 
       {
          if ( p ) free( p ) ;
          p = malloc( strlen(s) + 1 ) ;
          strcpy( p, s ) ;
       }
       return p ;
}

irreversibile (ad esempio, lettura da una pipe o socket).
E` proprio quando non e` nota a priori la dimensione dei
dati che si usa l'allocazione dinamica... no?
Ripeto... in questi casi creo una classe TDato che rappresenta
appunto i miei dati e le relative operazioni... la costruzione del dato
ed eventuale allocazione di memoria e` fatta nel costruttore
senza allocazione di buffer temporanei e inutili overhead: le
varie parti dell'oggetto sono allocate e inizializzate in un 
"colpo solo".
La mia risposta nei post precedenti era riferita al caso in
cui per qualche motivo non fosse possibile allocare e inizializzare
in un colpo solo tramite un factory method (che preferisco sia
un costruttore anche dal punto di vista sintattico, almeno quando 
possibile senza costi intollerabili). In questo caso preferisco appunto
che la XYZAllocaECalcola sia scissa in due funzioni (la prima magari
come overloading di new).
