[C] Lettura di una stringa con spazi (cioè senza scanf)

Atem1
Salve ragazzi, io sto provando l'algoritmo per leggere una stringa contente anche spazi ma il programma mi crasha.

Parte dichiarativa:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
char *get_string();


Main:
int main()
{
   char *s1;
   printf("Inserisci la stringa: ");
   s1=get_string();
   printf ("La stringa inserita e': %s", s1);
   return 0;
}


Funzione get_string:
char *get_string()
{
   char *s;
   char ch;
   int dim=0;
   while (ch=getchar()!='\n')
   {
       ++dim;
       s=(char*) realloc (s, dim*(sizeof(char)));
       *(s+dim-1)=ch;
   }
   *(s+dim)=0;
   return s;
}

Come mai il programma crasha subito dopo aver premuto l'invio?
Grazie mille per l'attenzione :)

Risposte
claudio862
"http://en.cppreference.com/w/c/memory/realloc":
Reallocates the given area of memory. It must be previously allocated by malloc(), calloc() or realloc() and not yet freed with free, otherwise, the results are undefined.

Non hai inizializzato "s", quindi la prima volta che chiami "realloc", usi una variabile non inizializzata. Inoltre devi allocare spazio anche per il carattere nullo terminatore, quindi "(dim + 1) * sizeof(char)".

char *get_string()
{
char *s = (char*) malloc(1); // 1, perché contiene almeno il terminatore finale.
char ch;
int dim = 0;
while ((ch = getchar()) != '\n')
{
++dim;
s = (char*) realloc (s, (dim + 1) * (sizeof(char)));
*(s + dim - 1) = ch;
}
*(s + dim) = 0;
return s;
}


Puoi anche usare la funzione fgets. A differenza di quella che hai scritto tu, non legge stringhe di lunghezza arbitrarie, ma solo fino ad una massima dimensione (specificata con il secondo parametro).

apatriarca
L'espressione
ch=getchar()!='\n'

viene valutata nel seguente modo
ch= (getchar() != '\n')

mentre credo tu volessi valutarla come segue
(ch = getchar()) != '\n'

apatriarca
Riguardo all'uso di realloc.. Allocare un byte è inutile e chiamare realloc all'interno di un ciclo di questo tipo è un enorme spreco di risorse. Di fatto molte librerie sono in grado di allocare solo multipli di 4-8 byte e il blocco viene (quasi) sempre preceduto da almeno 4-8 byte di header. Vista l'enorme quantità di RAM che si ha a disposizione, tanto vale allocare molta più memoria, per esempio 64 bytes, e poi riallocare solo quando non c'è più posto. La nuova dimensione si può decidere in base all'uso ma può essere qualcosa come un multiplo della dimensione corrente oppure si può decidere di aggiungere una quantità fissa. Il codice scritto in questo modo è un po' più utile di questo classico che viene insegnato a lezione e più utilizzabile in progetti reali.

Atem1
"apatriarca":
L'espressione
mentre credo tu volessi valutarla come segue
(ch = getchar()) != '\n'

Sìsì hai ragione, mi sono dimenticato le parentesi xD
"claudio86":
[quote="http://en.cppreference.com/w/c/memory/realloc"]Reallocates the given area of memory. It must be previously allocated by malloc(), calloc() or realloc() and not yet freed with free, otherwise, the results are undefined.

Non hai inizializzato "s", quindi la prima volta che chiami "realloc", usi una variabile non inizializzata. Inoltre devi allocare spazio anche per il carattere nullo terminatore, quindi "(dim + 1) * sizeof(char)".
[/quote]
Ahhh ok mi sfuggiva il fatto di dover allocare lo spazio per il carattere nullo terminatore e ti ringrazio infinitamente perchè da solo non ci sarei mai arrivato visto che ieri ci sono stato tutta la notte a cercare di capire perchè non andava.

Puoi anche usare la funzione fgets. A differenza di quella che hai scritto tu, non legge stringhe di lunghezza arbitrarie, ma solo fino ad una massima dimensione (specificata con il secondo parametro).

Ecco, questa è un'altra cosa che non m'ha fatto dormire!
Infatti io non capisco come usare la fgets DOPO aver usato la scanf ed è lo stesso identico problema che mi da quella funzione get_string();
Infatti get_string() ora mi funziona a meno che io non tolga i commenti a quelle tre righe che ho commentato:
int main()
{
   //int n;
  //printf ("Insericsi un numero: "); 
  //scanf("%d", &n);
  char *s1;
  printf("Inserisci la stringa: ");
  s1=get_string();
  printf ("La stringa inserita e': %s", s1);
  return 0;
}

char *get_string()
{
   char *s=malloc(1);
   char ch;
   int dim=0;
   while ((ch=getchar())!='\n')
   {
       ++dim;
       s=(char*) realloc (s, (dim+1)*(sizeof(char)));
       *(s+dim-1)=ch;
   }
   *(s+dim)=0;
   return s;
}

Perchè dopo lo scanf, la funzione get_string() memorizza una stringa vuota?
Mi succede la stessa cosa con fgets....

Atem1
"apatriarca":
Riguardo all'uso di realloc.. Allocare un byte è inutile e chiamare realloc all'interno di un ciclo di questo tipo è un enorme spreco di risorse. Di fatto molte librerie sono in grado di allocare solo multipli di 4-8 byte e il blocco viene (quasi) sempre preceduto da almeno 4-8 byte di header. Vista l'enorme quantità di RAM che si ha a disposizione, tanto vale allocare molta più memoria, per esempio 64 bytes, e poi riallocare solo quando non c'è più posto. La nuova dimensione si può decidere in base all'uso ma può essere qualcosa come un multiplo della dimensione corrente oppure si può decidere di aggiungere una quantità fissa. Il codice scritto in questo modo è un po' più utile di questo classico che viene insegnato a lezione e più utilizzabile in progetti reali.


Sì sì infatti io poi penso di usare la fgets(), solo che inizialmente voglio capire come far funzionare le cose che non riesco a far funzionare xD

apatriarca
In generale non si dovrebbero mischiare lettura per riga e lettura di file formattati come nel tuo caso. Il problema è che scanf non elimina gli spazi o i caratteri di "a capo" dal buffer di lettura (da quello che hai insomma letto da console) per cui quando viene seguita dalla lettura di una riga, il primo carattere letto sarà un carattere di a capo (probabilmente) e quindi restituirà una stringa vuota. Le soluzioni sono due:
1. Leggere da file usando solo fgets o get_string o equivalente e poi usare sscanf per la lettura di dati formattati in modo particolare (in genere è il modo consigliato);
2. Far precedere ogni lettura con fgets e get_string preceduta da uno scanf con una qualche funzione che ignora il resto della riga attualmente contenuta nel buffer.

Atem1
"apatriarca":
In generale non si dovrebbero mischiare lettura per riga e lettura di file formattati come nel tuo caso. Il problema è che scanf non elimina gli spazi o i caratteri di "a capo" dal buffer di lettura (da quello che hai insomma letto da console) per cui quando viene seguita dalla lettura di una riga, il primo carattere letto sarà un carattere di a capo (probabilmente) e quindi restituirà una stringa vuota. Le soluzioni sono due:
1. Leggere da file usando solo fgets o get_string o equivalente e poi usare sscanf per la lettura di dati formattati in modo particolare (in genere è il modo consigliato);
2. Far precedere ogni lettura con fgets e get_string preceduta da uno scanf con una qualche funzione che ignora il resto della riga attualmente contenuta nel buffer.


Ok grazie mille, allora io ho fatto così e funziona:

int main()
{
    int n;
    char *s0;
    printf ("Insericsi un numero: ");
    fgets(s0, 10 ,stdin);
    n=atoi(s0);
    printf ("Il numero e' %d \n", n);
    char *s1;
    printf("Inserisci la stringa: ");
    s1=get_string();
    printf ("La stringa inserita e': %s", s1);

    return 0;
}



Quindi chiedo se in generale va bene fare così, oppure se esistono modi migliori.

apatriarca
I metodi comunemente adottati te li ho già detti, in generale è meglio leggere una riga per volta e poi interpretare i dati letti separatamente. È normalmente la soluzione più flessibile e robusta da adottare.

Hai dimenticato di chiamare free sulla stringa restituita da get_string..

Atem1
"apatriarca":
I metodi comunemente adottati te li ho già detti, in generale è meglio leggere una riga per volta e poi interpretare i dati letti separatamente. È normalmente la soluzione più flessibile e robusta da adottare.
Hai dimenticato di chiamare free sulla stringa restituita da get_string..

Sì, ora c'è un'altra cosa che non capisco e cioè ho scritto 2 programmi praticamente uguali ma uno funziona mentre l'altro no. Questi 2 programmi hanno in comune la parte dichiarativa e la funzione mentre i MAIN sono quasi simili, cioè...


Parte dichiarativa per entrambi i programmi:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
int inserisci_numero(char* domanda);



L'unica funzione che ho scritto per entrambi i programmi è questa:
int inserisci_numero(char* domanda)
{
    int n;
    char *s0;
    printf ("%s", domanda);
    fgets(s0, 10 ,stdin);
    n=atoi(s0);
    return n;
}




Questi 2 programmi di diverso hanno solo il MAIN.

MAIN1:
int main()
{

    int num;
    num=inserisci_numero("Inserisci un numero: ");
    printf ("%d", num);
    return 0;
}



MAIN2:
int main()
{

    int scelta=0;
    while (scelta!=4)
    {
        printf ("Menu: \n\n");
        printf ("1: Visualizza caratteri maiuscoli \n");
        printf ("2: Concatena due stringhe \n");
        printf ("3: Trova la posizione di una sottostringa \n");
        printf ("4: Esci \n \n");

        do
        {
          scelta=inserisci_numero("Che scegli? ");
        } while ((scelta<1) || (scelta>4));

    }


    return 0;
}




I 2 MAIN sono praticamente uguali.
Solo che MAIN1 funziona, mentre MAIN2 crasha dopo aver inserito il valore di "scelta".
Cioè MAIN2 crasha quando premo INVIO. Perchè?

Atem1
Ok sono riuscito a risolvere il problema sul MAIN2...
int inserisci_numero(char* domanda)
{
    int n;
    char *s0=malloc(10);
    printf ("%s", domanda);
    fgets(s0, 10 ,stdin);
    n=atoi(s0);
    free(s0);
    return n;
}



Mi crashava perchè non avevo inizializzato s0...
Ora funziona, ma a questo punto mi chiedo, per quale motivo, quello che ho chiamato "MAIN1" funziona sebbene non abbia inizializzato s0. Perchè uno funziona mentre l'altro no se c'è lo stesso errore?

Vitalluni
se non inizializzi s0 non sai cosa può accadere.

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.