Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
C/C++ - Ritornare un puntatore ad una stringa senza deallocarla
Forum - C/C++ - Ritornare un puntatore ad una stringa senza deallocarla

Pagine: [ 1 2 3 4 ] Precedente | Prossimo
Avatar
()
Newbie


Messaggi:
Iscritto:

Segnala al moderatore
Postato alle 1:00
Giovedì, 01/01/1970
Salve a tutti, espongo il mio quesito.
Mi sto cimentando per hobby col c e nel frattempo vorrei imparare a programmare seriamente.

Vorrei creare il gioco dell'impiccato (data una frase xxx_xx_xx , la si deve indovinare).
Ho diversi dilemmi su alcuni punti. (Premesso che il programma, tranne qualche piccolo bug per lo più testuale, funziona, quindi la funzione "non dovrebbe" contenere errori, ma forse ne ho fatti in numero pari e si compensano XD.

Ecco una funzione del codice:
( una delle funzioni chiavi. Dato un puntatore ad una stringa,
ne torna un'altro che punta alla stringa contenente la frase celata.
La Stringa è controllata prima del passaggio, ha tutti i caratteri in minuscolo,
l'EOF finale e non continene lettere accentate ).
Perchè usare una funzione a parte e utilizzare i puntatori?
Per rende la funzione adoperabile anche in altri progetti (se la creassi dentro al main non potrei)
e perchè non so l'esatta lunghezza della stringa (viene pescata da un file con diverse frasi).


Codice sorgente - presumibilmente C/C++

  1. /****************************************************************************************
  2.  *  char* nascondiStr( char *stringa , size_t dim )                                     *
  3.  *                                                                                      *
  4.  *              Funzione che crea un puntatore alla stringa nascosta                    *
  5.  *                   del tipo (***_***) da una stringa normale                          *
  6.  *          Input                                                                       *
  7.  *              - (char)   *stringa    : stringa da nascondere                          *
  8.  *              - (size_t)  dim         : dimensione della stringa da nascondere           *
  9.  *          Output                                                                      *
  10.  *              - (*char)  puntatore di char alla stringa (***_***)                     *
  11.  *                                                                                      *
  12.  ***************************************************************************************/
  13.  
  14. /* librerie standard*/
  15. #include <stdlib.h>
  16. #include <string.h>
  17.  
  18. char* nascondiStr( char *stringa , size_t dim )
  19. {
  20.     /* DEFINIZIONI E INIZIALIZZAZIONI VARIABILI */
  21.     char  *stringaNascosta = NULL ; /* Stringa di appoggio */
  22.     size_t i = 0 ;                  /* contatore per il for */
  23.     /* DEFINIZIONI E INIZIALIZZAZIONI VARIABILI */
  24.  
  25.     /* alloco dim*char per la stringa da nascondere */
  26.     stringaNascosta = (char *)calloc( dim, sizeof( char ) );
  27.  
  28.     /* copia la stringa da nascondere su quella che verrà passata */
  29.     strcpy( stringaNascosta , stringa );
  30.  
  31.     /* ciclo per esaminare tutti i caratteri fino a EOF */
  32.     for( int i = 0; stringaNascosta[i] != '\0'; i++ ) {
  33.  
  34.         /* Se trova una lettera minuscola lo pone uguale a '*'  */
  35.         if( stringaNascosta[i] >= 97 && stringaNascosta[i] <= 122 ) {
  36.             stringaNascosta[i] = '*';
  37.         }/* fine if */
  38.     }/* fine for */
  39.  
  40.     return stringaNascosta;
  41. } /* fine main */



1 Punto: Per essere corretto, a fine funzione non dovrei deallocare la memoria allocata?
   Potrei farlo nel main, ma ciò non renderebbe la funzione difficilmente adoperabile altrove?

2 Punto: Per tenere traccia di tutte le modifiche ai file, che posso usare?
   Git mi pare eccessivo per pochi file.

3 Punto: I nomi variabili, commenti e identazione vanno bene?
   a fine file vorrei aggiungere pure un CHANGELOG che tiene conto delle varie versioni,
   ma non vorre appesantire troppo i sorgenti.

4 Punto: Ho spezzettato quasi tutto il programma in funzioni più piccole, fatto bene o male?
   troppe funzioni non rallentano troppo l'esecuzione?
   gli header e i file contenenti le funzioni devono avere lo stesso nome?
io ho creato un file .c per ogni che sto usando, e solo un header che racchiude tutti i prototipi.
Potrei accomunare varie funzioni tra loro, ma per ora ho preferito fare cosi,
almeno mi stampo le funzioni una ad una e le posso "correggere" anche a pc spento.

Per concludere dico che uso Visual Studio 2015 (versione free) ma ho anche lavorato con
Dev-c++ (perche è un ide in italiano) e un pochino con code::block (ma con cui non mi trovo)
Vorrei anche imparare a compilare da linea di comando in futuro
(ma se dite che sia necessario imparare a farlo fin da ora... mi cimento pure in questo).

Grazie fin da ora a tutti coloro volessero aiutarmi.



PM Quote
Avatar
nessuno (Normal User)
Guru^2


Messaggi: 5475
Iscritto: 03/01/2010

Segnala al moderatore
Postato alle 10:24
Giovedì, 21/01/2016
Elimina il parametro

size_t dim

perché non serve e usa la strlen senza dimenticare il carattere terminatore, quindi

stringaNascosta = (char *)calloc( strlen(stringa)+1, sizeof( char ) );

Ovviamente non devi deallocare prima di uscire altrimenti non hai fatto nulla ... la liberazione della memoria è a carico del chiamante.

Potresti anche semplificare il codice usando la funzione _strdup



Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
PM Quote
Avatar
()
Newbie


Messaggi:
Iscritto:

Segnala al moderatore
Postato alle 11:15
Giovedì, 21/01/2016
Testo quotato

Postato originariamente da nessuno:

Elimina il parametro

size_t dim

perché non serve e usa la strlen senza dimenticare il carattere terminatore, quindi

stringaNascosta = (char *)calloc( strlen(stringa)+1, sizeof( char ) );


Innanzitutto grazie per la risposta.
Mi sono portato dietro la dimensione di *stringa perche in vari punti del programma la utilizzo e volevo evitare diverse chiamate a strlen().


Testo quotato

:
Ovviamente non devi deallocare prima di uscire altrimenti non hai fatto nulla ... la liberazione della memoria è a carico del chiamante.


quello che volevo evitare, altrimenti, ad esempio,  se qualcun'altro la utilizzasse dovrebbe sapere che il chiamante deve deallocare la stringa. Anche io stesso, se la usassi tra un mese , potrei scordarlo XD (vabbe, io ho la documentazione in caso, ma vorrei qualcosa di più generale, se è possibile farlo)

Testo quotato

:
Potresti anche semplificare il codice usando la funzione _strdup


Non conoscevo la funzione, grazie del suggerimento, io per ora sto cercando di usare le librerie dello standard, ho appena letto che essa non ne fa parte.


EDIT1 & 2 : Correzioni al testo.


Ultima modifica effettuata da il 21/01/2016 alle 11:17
PM Quote
Avatar
nessuno (Normal User)
Guru^2


Messaggi: 5475
Iscritto: 03/01/2010

Segnala al moderatore
Postato alle 13:32
Giovedì, 21/01/2016
Testo quotato


Mi sono portato dietro la dimensione di *stringa perche in vari punti del programma la utilizzo e volevo evitare diverse chiamate a strlen().



Beh, non esageriamo, la chiamata a strlen non è così pesante ... così invece la tua funzione è legata alla chiamata di strlen prima per ogni stringa ...

Testo quotato

quello che volevo evitare



E' un discorso che non ha senso. La stringa restituita deve pur essere in memoria.


Ultima modifica effettuata da nessuno il 21/01/2016 alle 13:32


Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
PM Quote
Avatar
()
Newbie


Messaggi:
Iscritto:

Segnala al moderatore
Postato alle 14:31
Giovedì, 21/01/2016
ok per strlen...
Ecco la nuova funzione

Codice sorgente - presumibilmente C++

  1. /* librerie standard*/
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. char* nascondiStr( char *stringa )
  6. {
  7.     /* DEFINIZIONI E INIZZIALIZZAZIONI VARIABILI */
  8.     char  *stringaNascosta = NULL ; /* Stringa di appoggio */
  9.     size_t i = 0 ;                  /* contatore per il for */
  10.     /* DEFINIZIONI E INIZZIALIZZAZIONI VARIABILI */
  11.  
  12.     /* alloco dim*char per la stringa da nascondere */
  13.     stringaNascosta = (char *)calloc( strlen(stringa) + 1, sizeof( char ) );
  14.    
  15.     /* Controllo allocazione memoria */
  16.         if( stringaNascosta == NULL ) {
  17.         printf("Non riesco ad allocare memoria\n");
  18.         exit(1);
  19.     }
  20.          
  21.     /* copia la stringa da nascondere su quella che verrà passata */
  22.     strcpy( stringaNascosta , stringa );
  23.  
  24.     /* ciclo per esaminare tutti i caratteri fino a EOF */
  25.     for( int i = 0; stringaNascosta[i] != '\0'; i++ ) {
  26.  
  27.         /* Se trova una lettera minuscola lo pone uguale a '*'  */
  28.         if( stringaNascosta[i] >= 97 && stringaNascosta[i] <= 122 ) {
  29.             stringaNascosta[i] = '*';
  30.         }/* fine if */
  31.     }/* fine for */
  32.  
  33.     return stringaNascosta;
  34. } /* fine main */


ho aggiunto il controllo dell'allocazione che mi ero scordato

Comunque, quello che chiedevo , scusate se mi sono espresso male, è se si poteva evitare di dover allocare/deallocare il puntatore, ad esempio come in questa caso:
Codice sorgente - presumibilmente C++

  1. /* librerie standard*/
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. void nascondiStr( char *stringa, char  *stringaNascosta )
  6. {
  7.     /* DEFINIZIONI E INIZZIALIZZAZIONI VARIABILI */
  8.     size_t i = 0 ;                  /* contatore per il for */
  9.     /* DEFINIZIONI E INIZZIALIZZAZIONI VARIABILI */
  10.          
  11.     /* copia la stringa da nascondere su quella che verrà passata */
  12.     strcpy( stringaNascosta , stringa );
  13.  
  14.     /* ciclo per esaminare tutti i caratteri fino a EOF */
  15.     for( int i = 0; stringaNascosta[i] != '\0'; i++ ) {
  16.  
  17.         /* Se trova una lettera minuscola lo pone uguale a '*'  */
  18.         if( stringaNascosta[i] >= 97 && stringaNascosta[i] <= 122 ) {
  19.             stringaNascosta[i] = '*';
  20.         }/* fine if */
  21.     }/* fine for */
  22.  
  23. } /* fine main */
  24.  
  25.  
  26. /* allocando la stringa prima nel main, cioè passo le due stringhe dal main per riferimento
  27. ...
  28.    stringaNascosta = (char *)calloc( strlen(stringa) + 1, sizeof( char ) );
  29.    nascondiStr(  *stringa, *stringaNascosta ) ;
  30. ...
  31.  
  32. */



o in questo

Codice sorgente - presumibilmente C++

  1. /* librerie standard*/
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. /* qui passo solo */
  6. void nascondiStr( char *stringaNascosta )
  7. {
  8.     /* DEFINIZIONI E INIZZIALIZZAZIONI VARIABILI */
  9.     size_t i = 0 ;                  /* contatore per il for */
  10.     /* DEFINIZIONI E INIZZIALIZZAZIONI VARIABILI */
  11.    
  12.     /* ciclo per esaminare tutti i caratteri fino a EOF */
  13.     for( int i = 0; stringaNascosta[i] != '\0'; i++ ) {
  14.  
  15.         /* Se trova una lettera minuscola lo pone uguale a '*'  */
  16.         if( stringaNascosta[i] >= 97 && stringaNascosta[i] <= 122 ) {
  17.             stringaNascosta[i] = '*';
  18.         }/* fine if */
  19.     }/* fine for */
  20.  
  21. } /* fine main */
  22.  
  23. /*  NEL MAIN
  24. ...
  25.     stringaNascosta = (char *)calloc( strlen(stringa) + 1, sizeof( char ) );
  26.         strcpy( *stringaNascosta, *stringa );
  27.         nascondiStr( *stringaNascosta ) ;
  28. ...
  29. */



EDIT:
quasi certamente un'allocazione/deallocazione sarà necessaria, ma entrambe nella stessa funzione è da preferirsi per evitare eventuali dimenticanze.


Ultima modifica effettuata da il 21/01/2016 alle 14:34
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1457
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 15:30
Giovedì, 21/01/2016
Testo quotato

Postato originariamente da Mikelius:
Comunque, quello che chiedevo , scusate se mi sono espresso male, è se si poteva evitare di dover allocare/deallocare il puntatore


"Allocare un puntatore" è una frase senza alcun senso.
Non si allocano valori, si alloca spazio nell'heap, cosa tu faccia con quello spazio non importa.
quando crei una nuova istanza di classe con l'operatore new non allochi una classe, allochi spazio sufficiente per mantenere la classe.

Quello che tu non hai capito è come funziona un puntatore se passato come paramentro ad una funzione.
Quando tu passi a una funzione un puntatore stai passando il primo indirizzo di memoria dello spazio allocato (o non).
La funzione può usare quell'indirizzo di memoria per scrivere sulla memoria interessata!

Ovvio, hai detto alla funzione dov'è la memoria "originale"! *nota che con originale intendo la memoria allocata nell'heap o nello stack in cui vengono mantenuti i dati di interesse (leggibili/scrivibili dalla funzione).

Quindi..... La funzione non ha alcun bisogno di allocare memoria?...... Eh già!

Ma cosa succederebbe se tu allocassi nuova memoria all'interno della suddetta funzione e assegnassi l'indirizzo di base di quella memoria al puntatore parametro di ingresso della funzione? Il puntatore viene passato per valore, quindi ti ritroveresti memoria allocata per nulla, visto che al di fuori della funzione non esisterebbe alcun riferimento a quella memoria! (e come potrebbe? la memoria è stata allocata nella funzione e non ritornata.....).



PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1457
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 15:41
Giovedì, 21/01/2016
2 Punto: Per tenere traccia di tutte le modifiche ai file, che posso usare?
   Git mi pare eccessivo per pochi file.

4 Punto: Ho spezzettato quasi tutto il programma in funzioni più piccole, fatto bene o male?
   troppe funzioni non rallentano troppo l'esecuzione?
   gli header e i file contenenti le funzioni devono avere lo stesso nome?


La risposta al primo quesito è un parere personale.
2° Punto: git va benissimo, ma se vuoi qualcosa di più semplice e immediato subversion è ciò che fa per te.

4° Punto: Hai fatto bene: ne trae vantaggio la mantenibilità del codice e la sua organizzazione.
Se l'esecuzione viene rallentata dipende dal compilatore, ma in linea di massima i salti incondizionati introducono stalli nella pipeline di una CPU, ma i tempi di attesa sono nella maggior parte dei casi trascurabili.

Gli header vanno spezzati a seconda della classe di funzioni logiche a cui appartengono (e/o a cui appartengono le funzioni di cui si mantengono le firme). Si, vanno spezzati proprio come si divide un programma in funzioni, per la stessa identica ragione!

PM Quote
Avatar
()
Newbie


Messaggi:
Iscritto:

Segnala al moderatore
Postato alle 15:57
Giovedì, 21/01/2016
Testo quotato

Postato originariamente da TheDarkJuster:

Testo quotato

Postato originariamente da Mikelius:
Comunque, quello che chiedevo , scusate se mi sono espresso male, è se si poteva evitare di dover allocare/deallocare il puntatore


"Allocare un puntatore" è una frase senza alcun senso.
Non si allocano valori, si alloca spazio nell'heap, cosa tu faccia con quello spazio non importa.



Giusto, chiedo venia per la castroneria.
Sto studiando dal Deitel , onestamente i puntatori non sono fatti benissimo, potrei veramente aver capito male qualche concetto su di essi, sai per caso qualche testo in cui sono fatti bene?

Per la mia funzione quindi che mi consigli?


PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1457
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 17:08
Giovedì, 21/01/2016
Provo a spiegare io il concetto di puntatore in parole molto, molto, molto semplici.
hai un foglietto di carta. Si chiama bob. Su bob puoi scrivere ogni numero che è contenuto in 4 byte.

In pratica hai:
Codice sorgente - presumibilmente C/C++

  1. int bob;



Fino a qui nulla di male. I 4 bytes SONO bob. I 4 bytes sono allocati nello stack.
Ora.... hai un foglietto di carta uguale a bob, ma invece di scrivere i numeri su quel foglietto..... lo usi per ricordarti DOVE si trova bob!

Codice sorgente - presumibilmente C/C++

  1. int *dove_si_trova_bob = &bob;



L'operatore & in questo caso è usato per ottenere l'indirizzo in memoria di bob.

Da notare che bob e dove_si_trova_bob sono due cose completamente diverse!
dove_si_trova_bob sarà qualcosa tipo "a casa sua", mentre bob sarà per esempio 0xFF5603AB (un numero a caso che ci sta in 4 bytes).

bob e dove_si_trova_bob SONO due aree di memoria DIVERSE.

tu puoi modificare direttamente bob:
Codice sorgente - presumibilmente Plain Text

  1. bob = 0xFFFF03AC;



oppure puoi usare il suo indirizzo per trovare bob e poi modificarlo:
Codice sorgente - presumibilmente Plain Text

  1. *(dove_si_trova_bob) = 0xFFFF03AC;



Perchè quel *() ? Beh perchè dove_si_trova_bob è una serie di bytes. Se tu facessi:

Codice sorgente - presumibilmente Plain Text

  1. dove_si_trova_bob = 0xFFFF03AC;



avresti un promemoria a dove si trova billy (0xFFFF03AC), e avresti perso l'indirizzo di bob!

Da questo ragionamento puoi dedurre diverse cose:
dove_si_trova_bob è un puntatore e come tale è un numero che il sistema operativo usa per trovare bob.
Se modifichi (per errore) dove_si_trova_bob al posto di *(dove_si_trova_bob) non riuscirai più a trovare bob, se bob era memoria allocata sull'heap tramite malloc() di cui non hai altri riferimenti.

Vediamo:
Codice sorgente - presumibilmente C/C++

  1. int *dove_si_trova_bob = malloc(sizeof(int));
  2.  
  3.  //bob non è più spazio allocato nello stack. Ora è spazio allocato nell' heap. Però è pur sempre un foglietto di carta in cui ci stanno 4 byte!!!!
  4.  
  5.  //prima, quando era allocato nello stack, bob veniva "deallocato" automaticamente. Ora invece dobbiamo deallocarlo "a mano"
  6.  free(dove_si_trova_bob);
  7.  
  8.  //Però....... ora dove_si_trova_bob ha ancora il vecchio indirizzo di bob! Nonostante bob non sia più lì! Ora ci abita tizio, e non mi sembra giusto andare (accidentalmente) a disturbare tizio:
  9.  dove_si_trova_bob = (int*)NULL;



"Piccola curiosità" (importantissima). La dimensione di ogni puntatore è fissa, e dipende da come è fatta la CPU che eseguirà il programma. Non c'è differenza tra char*, void*, int*, struct dati* e ogni qualsiasi altro puntatore.
prova tu stesso:
Codice sorgente - presumibilmente C/C++

  1. if (sizeof(char*) == sizeof(int*)) {
  2.     printf("I puntatori hanno ugual dimensione");
  3. }


Questo perchè? Beh perchè la differenza di dimensione sta nella memoria allocata per mantenere i dati, non in quella che mantiene il riferimento ai dati!

Ma allora..... Perchè il tipo puntatore non è unico, ed esistono char*, void*, struct data*, int*, short* ecc....???
Perchè così il compilatore sa cosa vuoi fare della memoria puntata!!!
Codice sorgente - presumibilmente C/C++

  1. void* dove_si_trova_bob = &bob;
  2. int sosia_di_bob = *(dove_si_trova_bob);



C'è un problema....... Il compilatore non sa quanti dati copiare da bob a sosia_di_bob! un byte? due? quattro? otto? 3GB?
In realtà c'è di più da dire, ma mi sembra che questa sia una spiegazione abbastanza semplice da poter essere compresa senza troppo sforzo.

Scusa se ti ho annoiato. Ora, nelle mie intenzioni, dovresti riuscire a capire ciò che scrive il tuo libro.

PM Quote
Pagine: [ 1 2 3 4 ] Precedente | Prossimo