Como você permite a inserção de espaços usando o scanf?

129

Usando o seguinte código:

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

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

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

Um usuário pode digitar seu nome, mas quando ele digita um nome com um espaço como Lucas Aardvark, scanf()apenas corta tudo depois Lucas. Como faço para scanf()permitir espaços

Kredns
fonte
9
Observe que mais idiomático é 'malloc (sizeof (char) * 256 + 1)' ou 'malloc (256 + 1)' ou melhor ainda (assumindo que 'name' será usado estritamente localmente) 'char name [256 + 1 ] ' O '+1' pode atuar como um mneumônico para o terminador nulo, que precisa ser incluído na alocação.
Barry Kelly
@ Barry - Eu suspeito que sizeof(char) + 256foi um erro de digitação.
22611 Chris Lutz

Respostas:

186

As pessoas (e especialmente os iniciantes) nunca devem usar scanf("%s")ou gets()ou quaisquer outras funções que não possuam proteção contra estouro de buffer, a menos que você tenha certeza de que a entrada sempre terá um formato específico (e talvez nem isso).

Lembre-se do que scanfsignifica "digitalização formatada" e há muito pouco menos formatado que os dados inseridos pelo usuário. É ideal se você tiver controle total do formato dos dados de entrada, mas geralmente inadequado para a entrada do usuário.

Use fgets()(que possui proteção contra estouro de buffer) para inserir sua entrada em uma sequência e sscanf()avaliá-la. Como você deseja apenas o que o usuário digitou sem analisar, não é realmente necessário sscanf(), neste caso:

#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;
}
paxdiablo
fonte
1
Eu não sabia fgets(). Na verdade, parece mais fácil usar então scanf(). +1
Kredns 8/08/09
7
Se você apenas deseja obter uma linha do usuário, é mais fácil. Também é mais seguro, pois você pode evitar estouros de buffer. A família scanf é realmente útil para transformar uma string em coisas diferentes (como quatro caracteres e um int, por exemplo, com "% c% c% c% c% d"), mas, mesmo assim, você deve usar fgets e sscanf, não scanf, para evitar a possibilidade de estouro de buffer.
22420
4
Você pode colocar o tamanho máximo do buffer no formato scanf, você não pode colocar um em tempo de execução computado sem criar o formato no tempo de execução (não há o equivalente a * para printf, * é um modificador válido para scanf com outro comportamento: suprimindo a atribuição )
AProgrammer 08/08/09
Observe também que scanftem comportamento indefinido se a conversão numérica exceder o limite ( N1570 7.21.6.2p10 , última frase, redação inalterada desde C89), o que significa que nenhuma das scanffunções pode ser usada com segurança na conversão numérica de entrada não confiável.
Zwol 30/04/19
@ JonathanKomar e qualquer outra pessoa que estiver lendo isso no futuro: se o seu professor lhe scanfdisser que você deve usar uma tarefa, eles estão errados ao fazê-lo, e você pode dizer a eles que eu disse e se eles querem discutir comigo sobre isso , meu endereço de e-mail é facilmente encontrado no meu perfil.
Zwol 30/04/19
124

Experimentar

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

Espero que ajude.

Kelly Gendron
fonte
10
(1) Obviamente, para aceitar espaços, você precisa colocar um espaço na classe de personagem. (2) Observe que 10 é o número máximo de caracteres que serão lidos; portanto, str deve apontar para um buffer de tamanho 11 pelo menos. (3) O final s aqui não é uma diretiva de formato, mas o scanf tentará aqui corresponder exatamente. O efeito será visível em uma entrada como 1234567890s onde os s serão consumidos, mas não colocará onde. Uma outra letra não será consumida. Se você colocar outro formato após o s, ele será lido apenas se houver um s a ser correspondido.
AProgrammer 08/08/09
Outro problema em potencial, o uso de - em outro local que não o primeiro ou o último é a implementação definida. Geralmente, é usado para intervalos, mas o que o intervalo designa depende do conjunto de caracteres. EBCDIC tem buracos na carta faixas e até mesmo ao assumir um ASCII derivado charsets é ingênuo pensar que todas as letras minúsculas estão na faixa az ...
AProgrammer
1
"% [^ \ n]" tem o mesmo problema que gets (), estouro de buffer. Com a captura adicional de que a \ n final não é lida; isso ficará oculto pelo fato de que a maioria dos formatos começa ignorando os espaços em branco, mas [não é um deles. Eu não entendo a instância em usar scanf para ler seqüências de caracteres.
AProgrammer 9/08/09
1
Removido o sfinal da string de entrada, pois é supérfluo e incorreto em certos casos (como apontado nos comentários anteriores). [é o seu próprio especificador de formato, em vez de alguma variação desse s.
22415
54

Este exemplo usa um conjunto de varredura invertido, portanto o scanf continua recebendo valores até encontrar uma nova linha '\ n' -, para que os espaços sejam salvos também

#include <stdio.h>

int main (int argc, char const *argv[])
{
    char name[20];
    scanf("%[^\n]s",name);
    printf("%s\n", name);
    return 0;
}
SVA
fonte
1
Cuidado com estouros de buffer. Se o usuário escrever um "nome" com 50 caracteres, o programa provavelmente falhará.
Brunoais
3
Como você sabe o tamanho do buffer, você pode usar %20[^\n]spara evitar buffer overflows
osvein
45 pontos e ninguém apontou o óbvio culto de carga de ter slá!
Antti Haapala
22

Você pode usar isso

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

Ou isto

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

Vitim.us
fonte
1
Eu não testei, mas com base em outras respostas nesta mesma página, eu acredito que o tamanho do buffer correto para scanf no seu exemplo seria: scanf("%19[^\n]", name);(ainda +1 para a resposta concisa)
Dr Beco
1
Apenas como uma observação lateral, sizeof(char)é por definição sempre 1, então não há necessidade de multiplicar por ela.
22416
8

Não use scanf()para ler strings sem especificar a largura do campo. Você também deve verificar os valores de retorno para erros:

#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);
}

Como alternativa, use 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);
}
Christoph
fonte
6

Você pode usar a fgets()função para ler uma string ou usar scanf("%[^\n]s",name);para que a leitura da string termine ao encontrar um caractere de nova linha.

Anshul garg
fonte
Cuidado para que isso não impeça o estouro de buffer #
3055
snão pertence lá
Antti Haapala
5

getline()

Agora parte do POSIX, no entanto.

Ele também cuida do problema de alocação de buffer que você perguntou anteriormente, embora seja necessário cuidar da freememória.

dmckee --- gatinho ex-moderador
fonte
Padrão? Na referência que você cita: "Ambos getline () e getdelim () são extensões GNU."
AProgrammer 09/08/2009
1
O POSIX 2008 adiciona getline. Então o GNU foi adiante e mudou seus cabeçalhos para o glibc na versão 2.9, e está causando problemas para muitos projetos. Não é um link definitivo, mas veja aqui: bugzilla.redhat.com/show_bug.cgi?id=493941 . Quanto à página de manual on-line, peguei a primeira que o google encontrou.
dmckee --- ex-moderador gatinho
3

Se alguém ainda está procurando, aqui está o que funcionou para mim - ler um comprimento arbitrário de string, incluindo espaços.

Agradecemos a muitos pôsteres na web por compartilhar esta solução simples e elegante. Se funcionar, o crédito vai para eles, mas quaisquer erros são meus.

char *name;
scanf ("%m[^\n]s",&name);
printf ("%s\n",name);
Sempre aprendendo
fonte
2
Vale a pena notar que esta é uma extensão POSIX e não existe no padrão ISO. Para garantir a integridade, você provavelmente também deve verificar errnoe limpar a memória alocada.
paxdiablo
snão pertence a esse local após o scanset
Antti Haapala
1

Você pode usar scanfpara esse fim com um pequeno truque. Na verdade, você deve permitir a entrada do usuário até que ele pressione Enter ( \n). Isso considerará todos os personagens, incluindo o espaço . Aqui está um exemplo:

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;
}

Como isso funciona? Quando o usuário insere caracteres da entrada padrão, eles serão armazenados na variável de sequência até o primeiro espaço em branco. Depois disso, o restante da entrada permanecerá no fluxo de entrada e aguarde o próximo scanf. Em seguida, temos um forloop que pega char por char do fluxo de entrada (até \n) e os anexa ao final da string variável , formando assim uma string completa igual à entrada do usuário do teclado.

Espero que isso ajude alguém!

akelec
fonte
Sujeito a estouro de buffer.
paxdiablo
0

Embora você realmente não deva usar scanf()esse tipo de coisa, porque há chamadas muito melhores, como gets()ou getline(), isso pode ser feito:

#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;
}
Ed Zavada
fonte
2
Há uma razão pela qual getsfoi preterido e removido ( stackoverflow.com/questions/30890696/why-gets-is-deprecated ) do padrão. É ainda pior que scanfporque pelo menos o último tem maneiras de torná-lo seguro.
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;
 }
venkata sandeep
fonte
4
Você pode explicar por que essa é a resposta correta? Por favor, não poste respostas somente de código.
Theo
snão pertence lá após a scanset
Antti Haapala