In che modo è possibile inserire spazi usando scanf?


129

Utilizzando il seguente codice:

char *name = malloc(sizeof(char) + 256); 

printf("What is your name? ");
scanf("%s", name);

printf("Hello %s. Nice to meet you.\n", name);

Un utente può inserire il proprio nome ma quando inserisce un nome con uno spazio simile Lucas Aardvark, scanf()taglia semplicemente tutto dopo Lucas. Come faccio a scanf()consentire gli spazi


9
Nota che più idiomatico è 'malloc (sizeof (char) * 256 + 1)', o 'malloc (256 + 1)', o anche meglio (supponendo che 'name' sarà usato rigorosamente localmente) 'char name [256 + 1 ]'. Il "+1" può fungere da pneumonico per il terminatore null, che deve essere incluso nell'allocazione.
Barry Kelly,

@ Barry - Sospetto sizeof(char) + 256fosse un errore di battitura.
Chris Lutz,

Risposte:


186

Le persone (e in particolare i principianti) non dovrebbero mai usare scanf("%s")o gets()altre funzioni che non dispongono della protezione da buffer overflow, a meno che non si sappia per certo che l'input avrà sempre un formato specifico (e forse nemmeno allora).

Ricorda che scanfsta per "scansione formattata" e c'è poco meno formattato dei dati inseriti dall'utente. È ideale se si ha il controllo totale del formato dei dati di input ma generalmente non è adatto all'input dell'utente.

Utilizzare fgets()(che ha la protezione da overflow del buffer) per ottenere l'input in una stringa e sscanf()per valutarlo. Dal momento che vuoi solo ciò che l'utente ha inserito senza analizzare, sscanf()in questo caso non hai davvero bisogno :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Maximum name size + 1. */

#define MAX_NAME_SZ 256

int main(int argC, char *argV[]) {
    /* Allocate memory and check if okay. */

    char *name = malloc(MAX_NAME_SZ);
    if (name == NULL) {
        printf("No memory\n");
        return 1;
    }

    /* Ask user for name. */

    printf("What is your name? ");

    /* Get the name, with size limit. */

    fgets(name, MAX_NAME_SZ, stdin);

    /* Remove trailing newline, if there. */

    if ((strlen(name) > 0) && (name[strlen (name) - 1] == '\n'))
        name[strlen (name) - 1] = '\0';

    /* Say hello. */

    printf("Hello %s. Nice to meet you.\n", name);

    /* Free memory and exit. */

    free (name);
    return 0;
}

1
Non lo sapevo fgets(). In realtà sembra più facile da usare allora scanf(). +1
Kredns,

7
Se vuoi solo ottenere una linea dall'utente, è più facile. È anche più sicuro poiché puoi evitare overflow del buffer. La famiglia scanf è davvero utile per trasformare una stringa in cose diverse (come quattro caratteri e un int per esempio con "% c% c% c% c% d") ma, anche in questo caso, dovresti usare fgets e sscanf, non scanf, per evitare la possibilità di overflow del buffer.
paxdiablo,

4
Puoi mettere le dimensioni massime del buffer nel formato scanf, non puoi mettere uno calcolato in runtime senza creare il formato in fase di runtime (non c'è l'equivalente di * per printf, * è un modificatore valido per scanf con un altro comportamento: sopprimere l'assegnazione ).
AProgrammer

Si noti inoltre che scanfha un comportamento indefinito in caso di overflow della conversione numerica ( N1570 7.21.6.2p10 , ultima frase, formulazione invariata rispetto a C89), il che significa che nessuna delle scanffunzioni può essere tranquillamente utilizzata per la conversione numerica di input non attendibili.
zwol,

@JonathanKomar e chiunque altro leggerà questo in futuro: se il tuo professore ti ha detto che dovevi usare scanfin un compito, si sbagliavano a farlo, e potresti dire loro che l'ho detto, e se vogliono litigare con me , il mio indirizzo email è facilmente reperibile dal mio profilo.
zwol,

124

Provare

char str[11];
scanf("%10[0-9a-zA-Z ]", str);

Spero che aiuti.


10
(1) Ovviamente per accettare gli spazi, devi inserire uno spazio nella classe del personaggio. (2) Si noti che il 10 è il numero massimo di caratteri che verranno letti, quindi str deve puntare almeno a un buffer di dimensione 11. (3) Gli ultimi qui non sono una direttiva di formato, ma scanf proverà qui ad abbinarli esattamente. L'effetto sarà visibile su una voce come 1234567890s in cui le s verranno consumate ma non verranno posizionate dove. Un'altra lettera non verrà consumata. Se inserisci un altro formato dopo la s, verrà letto solo se c'è una s da abbinare.
AProgrammer

Un altro potenziale problema, l'uso di - in un posto diverso dal primo o dall'ultimo, è l'implementazione definita. Di solito, viene utilizzato per gli intervalli, ma ciò che l'intervallo designato dipende dal set di caratteri. EBCDIC ha buchi negli intervalli di lettere e anche quando si assume un carattere derivato ASCII è ingenuo pensare che tutte le lettere minuscole siano nell'intervallo az ...
AProgrammer

1
"% [^ \ n]" presenta lo stesso problema di gets (), buffer overflow. Con la cattura aggiuntiva che il \ n finale non viene letto; questo sarà nascosto dal fatto che la maggior parte dei formati inizia saltando gli spazi bianchi, ma [non è uno di questi. Non capisco l'istanza nell'uso di scanf per leggere le stringhe.
AProgrammer

1
Rimosso sdalla fine della stringa di input poiché in alcuni casi è sia superfluo che errato (come sottolineato nei commenti precedenti). [è il proprio identificatore di formato piuttosto che una variazione di squello.
paxdiablo,

54

Questo esempio usa uno scanset invertito, quindi scanf continua ad assumere valori fino a quando non incontra una '\ n' - newline, così anche gli spazi vengono salvati

#include <stdio.h>

int main (int argc, char const *argv[])
{
    char name[20];
    scanf("%[^\n]s",name);
    printf("%s\n", name);
    return 0;
}

1
Attento agli overflow del buffer. Se l'utente scrive un "nome" con 50 caratteri, il programma probabilmente si arresterà in modo anomalo.
brunoais,

3
Come sapete le dimensioni del buffer, è possibile utilizzare %20[^\n]sper evitare overflow del buffer
osvein,

45 punti e nessuno ha sottolineato l'ovvio culto del carico di averli slì!
Antti Haapala,

22

Puoi usare questo

char name[20];
scanf("%20[^\n]", name);

O questo

void getText(char *message, char *variable, int size){
    printf("\n %s: ", message);
    fgets(variable, sizeof(char) * size, stdin);
    sscanf(variable, "%[^\n]", variable);
}

char name[20];
getText("Your name", name, 20);

DEMO


1
Non ho testato, ma in base ad altre risposte in questa stessa pagina, credo che la dimensione del buffer corretta per scanf nel tuo esempio sarebbe: scanf("%19[^\n]", name);(ancora +1 per la risposta concisa)
Dr Beco

1
Proprio come una nota a margine, sizeof(char)per definizione è sempre 1, quindi non è necessario moltiplicarlo.
paxdiablo,

8

Non usare scanf()per leggere stringhe senza specificare una larghezza del campo. È inoltre necessario verificare la presenza di errori nei valori restituiti:

#include <stdio.h>

#define NAME_MAX    80
#define NAME_MAX_S "80"

int main(void)
{
    static char name[NAME_MAX + 1]; // + 1 because of null
    if(scanf("%" NAME_MAX_S "[^\n]", name) != 1)
    {
        fputs("io error or premature end of line\n", stderr);
        return 1;
    }

    printf("Hello %s. Nice to meet you.\n", name);
}

In alternativa, utilizzare fgets():

#include <stdio.h>

#define NAME_MAX 80

int main(void)
{
    static char name[NAME_MAX + 2]; // + 2 because of newline and null
    if(!fgets(name, sizeof(name), stdin))
    {
        fputs("io error\n", stderr);
        return 1;
    }

    // don't print newline
    printf("Hello %.*s. Nice to meet you.\n", strlen(name) - 1, name);
}

6

È possibile utilizzare la fgets()funzione per leggere una stringa o utilizzare in scanf("%[^\n]s",name);tal modo la lettura della stringa termina quando si incontra un carattere di nuova riga.


Attenzione che questo non impedisce il sovraccarico del buffer
brunoais,

snon appartiene lì
Antti Haapala il

5

getline()

Ora parte di POSIX, nondimeno.

Si occupa anche del problema di allocazione del buffer di cui hai chiesto in precedenza, anche se devi occuparti della freememoria.


Standard? Nel riferimento citi: "Sia getline () che getdelim () sono estensioni GNU."
Approgrammatore

1
POSIX 2008 aggiunge getline. Quindi GNU è andato avanti e ha cambiato le intestazioni per glibc intorno alla versione 2.9, e sta causando problemi a molti progetti. Non un link definitivo, ma guarda qui: bugzilla.redhat.com/show_bug.cgi?id=493941 . Per quanto riguarda la pagina man on-line, ho preso il primo trovato da Google.
dmckee --- ex gattino moderatore

3

Se qualcuno sta ancora cercando, ecco cosa ha funzionato per me: leggere una lunghezza arbitraria di stringa compresi gli spazi.

Grazie a molti poster sul web per aver condiviso questa soluzione semplice ed elegante. Se funziona il merito va a loro ma eventuali errori sono miei.

char *name;
scanf ("%m[^\n]s",&name);
printf ("%s\n",name);

2
Vale la pena notare che questa è un'estensione POSIX e non esiste nello standard ISO. Per completezza, probabilmente dovresti anche controllare errnoe ripulire anche la memoria allocata.
paxdiablo,

snon appartiene lì dopo lo scanset
Antti Haapala il

1

È possibile utilizzare scanfa questo scopo con un piccolo trucco. In realtà, dovresti consentire l'input dell'utente fino a quando l'utente non preme Enter ( \n). Questo prenderà in considerazione ogni personaggio, incluso lo spazio . Ecco un esempio:

int main()
{
  char string[100], c;
  int i;
  printf("Enter the string: ");
  scanf("%s", string);
  i = strlen(string);      // length of user input till first space
  do
  {
    scanf("%c", &c);
    string[i++] = c;       // reading characters after first space (including it)
  } while (c != '\n');     // until user hits Enter
  string[i - 1] = 0;       // string terminating
return 0;
}

Come funziona? Quando l'utente immette caratteri dall'input standard, questi verranno archiviati nella variabile stringa fino al primo spazio vuoto. Successivamente, il resto della voce rimarrà nel flusso di input e attenderà il prossimo scanf. Quindi, abbiamo un forciclo che prende char per char dal flusso di input (fino a \n) e li aggiunge alla fine della stringa variabile , formando così una stringa completa come l'input dell'utente dalla tastiera.

Spero che questo possa aiutare qualcuno!


Soggetto a buffer overflow.
paxdiablo,

0

Anche se non dovresti davvero usarlo scanf()per questo genere di cose, perché ci sono chiamate molto migliori come gets()o getline(), può essere fatto:

#include <stdio.h>

char* scan_line(char* buffer, int buffer_size);

char* scan_line(char* buffer, int buffer_size) {
   char* p = buffer;
   int count = 0;
   do {
       char c;
       scanf("%c", &c); // scan a single character
       // break on end of line, string terminating NUL, or end of file
       if (c == '\r' || c == '\n' || c == 0 || c == EOF) {
           *p = 0;
           break;
       }
       *p++ = c; // add the valid character into the buffer
   } while (count < buffer_size - 1);  // don't overrun the buffer
   // ensure the string is null terminated
   buffer[buffer_size - 1] = 0;
   return buffer;
}

#define MAX_SCAN_LENGTH 1024

int main()
{
   char s[MAX_SCAN_LENGTH];
   printf("Enter a string: ");
   scan_line(s, MAX_SCAN_LENGTH);
   printf("got: \"%s\"\n\n", s);
   return 0;
}

2
C'è un motivo per cui è getsstato deprecato e rimosso ( stackoverflow.com/questions/30890696/why-gets-is-deprecated ) dallo standard. E 'ancora peggio che scanfperché almeno quest'ultimo ha modi per renderlo sicuro.
paxdiablo,

-1
/*reading string which contains spaces*/
#include<stdio.h>
int main()
{
   char *c,*p;
   scanf("%[^\n]s",c);
   p=c;                /*since after reading then pointer points to another 
                       location iam using a second pointer to store the base 
                       address*/ 
   printf("%s",p);
   return 0;
 }

4
Puoi spiegare perché questa è la risposta corretta? Si prega di non pubblicare risposte solo in codice.
Theo,

snon appartiene lì dopo lo scanset
Antti Haapala il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.