Jump to content
Leudassdf

[Resolvido] Problema com código. Função fgets

Recommended Posts

Leudassdf

Boas pessoal,

Eu tenho o seguinte código:

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

int main()
{

printf("ola mundo\n");
char teste[35];
scanf("%35s", teste);
printf("%s", teste);
teste();
}
void teste(){
printf("ola mundo\n");
char teste2[35];
fgets(teste2,35,stdin);
printf("%s", teste2);
}

O código é so para simular um problema que tive anteriormente. Ou seja a função teste() deve fazer o mesmo que a função main mas com o fgets. No entanto ao executar o programa ele só lê o input do utilizador uma vez que é a do scanf.

Alguem me sabe explicar porque e uma maneira de resolver este problema?

Share this post


Link to post
Share on other sites
pwseo

O problema aqui é utilizares as funções fgets e scanf no mesmo programa sem tomar as devidas precauções. Passo a explicar: a função scanf lê todo o teu input até encontrar '\n' mas deixa esse último símbolo na stream de input -- tecnicamente, isto quer dizer que o '\n' não foi lido. Ora, quando tentas ler com a fgets, a primeira coisa que está no input é o '\n' lá deixado pela scanf, e a fgets dá-se por satisfeita -- pensa que introduziste um texto vazio e pressionaste logo <Enter>.

Em suma: as funções da família da scanf deixam as newlines no input, mas a fgets não, o que provoca incompatibilidades quando utilizadas no mesmo programa, uma a seguir à outra.

Vou deixar-te pesquisar um pouco sobre o que fazer para lidar com estes problemas.

Aproveito para fazer um pequeno comentário ao teu código: não podes declarar uma char[35] e de seguida invocar scanf("%35s", ...). Se a tua array só tem 35 elementos e tentares ler uma string com 35 caracteres, onde fica o '\0' terminal? (null-terminator).

Este problema não se verifica com a fgets porque (tal como está descrito na manpage da mesma), esta função lê no máximo N-1 caracteres, para qualquer N que lhe dês e escreve o '\0' na última posição do buffer que lhe deres.

Além disso, não indentaste correctamente o código, não declaraste a função teste antes da sua utilização:

#include ...

void teste(void);

int main(void) {
 ...

 return 0; /* Só omites esta linha em C99 e C11 */
}

void teste(void) {
 ...
}

Em C, funções sem argumentos devem ser declaradas explicitamente com um void na lista de argumentos; caso contrário, significa que aceitam qualquer número de argumentos (em C++ a coisa muda de figura).

Edited by pwseo

Share this post


Link to post
Share on other sites
Leudassdf

Antes de mais muito obrigado, pwseo, pela excelente explicação.

Quero dizer-te que investiguei um pouco e pelo menos encontrei uma solução que passa por limpar o buffer que é: fflush(stdin).

Pelos testes que fiz funcionou bem.

Outra solução é usar só fgets. No entanto eu já ouvi dizer que esta função está obsoleta. Isto é verdade? Qual é a função que devemos utilizar?

E penso que o scanf tambem está. Ou pelo menos existem pessoas que defendem que nao se deve utiliza-lo.

ja agora o \0 serve para terminar a string. mas isto influencia em que casos? é que se for só para imprimir o valor não encontrei nenhum erro...

Edited by Leudassdf

Share this post


Link to post
Share on other sites
pwseo

Utilizar fflush(stdin) (atenção, é stdin e não stdio) é uma solução errada porque embora funcione no teu compilador, não é algo que seja suportado pelo Standard que define a linguagem (o que signfica que qualquer compilador pode fazer o que quiser quando tu escreves fflush(stdin), desde fazer o que tu queres, passando por não fazer nada, até fazer o que tu não queres, como crashar o computador (o que ainda assim seria improvável)).

Relativamente às outras soluções, ambas são igualmente válidas e por uma questão de consistência do código, deverás optar por utilizar uma ou outra função, sem misturar as duas.

Caso seja mesmo mesmo necessário misturá-las, tens que ter cuidado sempre que usas scanf e manualmente livrar o input dos '\n' lá deixados. Mais uma vez, vou deixar-te o exercício de descobrir de que forma podemos ler o '\n' deixado pela scanf (procura por "using scanf and fgets together", por exemplo).

O '\0' termina a string. Imagina que tens uma char[1024] na qual escreves "Leudassdf". Neste caso, não acrescentas nenhum '\0'. Quando tentares imprimir a tua string, vais ver o seguinte:

LeudassdfASJFQ#$=FWdf09qj43520'340990ew qvxcvw $2309i r0909 aw0f asdg'q34t f....

Isto acontece porque quando reservaste a memória, ela continha dados (lixo) que depois é impresso pela printf. E por que motivo é impresso pela printf? Porque ela imprime todos os caracteres na memória precisamente até encontrar o '\0', parando apenas nesse ponto.

Ou seja, se a tua array contivesse "Leudassdf" seguido de um '\0', a printf imprimiria:

Leudassdf

Isto aconteceria independentemente do lixo presente nos restantes elementos da array.

Existem outros casos onde a ausência de um '\0' pode causar problemas: a função strlen conta os bytes ocupados por uma string até que apareça o terminador. Pegando no exemplo anterior, a strlen incrementaria sempre o seu resultado até que por acaso aparecesse um terminado na memória, muito provavelmente bem depois do final dos caracteres "Leudassdf", devolvendo assim um resultado incorrecto.

Muitos casos de buffer-overflow têm também origem em falhas que dizem respeito à terminação de strings e são habitualmente bugs subtis e difíceis de detectar. Por isso acredita, toma sempre cuidado com as strings e seus terminadores.

Acrescento ainda que no teu caso, o facto de não incluires espaço para o terminador desdobra-se em 2 problemas distintos: o primeiro diz respeito ao facto de teres uma string sem terminador, mas o segundo começa quando o terminador é escrito de qualquer forma, mesmo que seja fora da memória que tu forneceste para a string -- os 35 caracteres são escritos na array e o terminador é escrito fora dela, podendo corromper dados que não te pertencem. Claro que aqui seria apenas um byte, as consequências seriam potencialmente nulas... Mas imagina isto a uma escala maior: convém mesmo ter cuidado com estas coisas.

Edited by pwseo

Share this post


Link to post
Share on other sites
Leudassdf

Irei sim procurar.

Mas ja agora no fgets é possivel especificar o tipo de carateres que estamos a espera? Não pois não?

Share this post


Link to post
Share on other sites
pwseo

Não, não é possível. Mas se quiseres utilizar fgets podes ler o input para um buffer temporário e depois utilizar a função sscanf (atenção, é sscanf). Esta última irá funcionar tal como a scanf, mas em vez de ler o teu input, lê o buffer que lhe deres (onde a fgets terá colocado o teu input previamente).

Outra solução: podes utilizar sempre scanf. Algum motivo em especial para quereres utilizar fgets neste caso?

Edited by pwseo

Share this post


Link to post
Share on other sites
Leudassdf

Não, não é possível. Mas se quiseres utilizar fgets podes ler o input para um buffer temporário e depois utilizar a função sscanf (atenção, é sscanf). Esta última irá funcionar tal como a scanf, mas em vez de ler o teu input, lê o buffer que lhe deres (onde a fgets terá colocado o teu input previamente).

Outra solução: podes utilizar sempre scanf. Algum motivo em especial para quereres utilizar fgets neste caso?

sim. é pelo facto de permitir espacos. Mas pelo que ja me apercebi se colacar \n no scanf ele tambem o le. Correto? pelo menos na minha situação correu bem

o que queres dizer com "tipo de caracter" ?

É se é int ou char basicamente

Share this post


Link to post
Share on other sites
pwseo

Leudassdf,

A scanf pára ao primeiro espaço porque tu lhe dizes para fazer isso -- é isso que faz o especificador s. Se tentares fazer scanf("%34[^\n]", ...) irás ler 34 caracteres que não sejam o '\n', resolvendo assim o teu problema. O terminador será adicionado automaticamente no final.

Dica: por vezes é útil começar as strings de formato passadas à scanf (e suas amigas) com um espaço, assim: scanf(" %d", &number). Esse espaço inicial irá assegurar-se que caso haja whitespace no input (espaços, newlines) este é saltado à frente antes de tentarmos ler (neste caso) um número.

Edited by pwseo

Share this post


Link to post
Share on other sites
Leudassdf

Hoje já nao vou ter oportunidade de experimentar o scanf com os especificadores. Amanha dou um feedback. Um abraço e um muito obrigado pelos esclarecimentos.

Share this post


Link to post
Share on other sites
HappyHippyHippo

sim. é pelo facto de permitir espacos.

o scanf também dá para ler espaços. não saber como se faz não é resposta da impossibilidade de não o fazer:

 char line[256] = {0};

 printf(">> ");
 fflush(stdout);
 scanf("%256[^\n]", line);
 printf("readed : %s", line);


IRC : sim, é algo que ainda existe >> #p@p

Share this post


Link to post
Share on other sites
Leudassdf

Relativamente às outras soluções, ambas são igualmente válidas e por uma questão de consistência do código, deverás optar por utilizar uma ou outra função, sem misturar as duas.

Caso seja mesmo mesmo necessário misturá-las, tens que ter cuidado sempre que usas scanf e manualmente livrar o input dos '\n' lá deixados. Mais uma vez, vou deixar-te o exercício de descobrir de que forma podemos ler o '\n' deixado pela scanf (procura por "using scanf and fgets together", por exemplo).

Hoje descobri mais uma solução que permite " limpar" o input do "\n" sendo ela: scanf("%d %*d",variável); Pelo que consegui perceber o "*" lê um caráter extra no entanto não o adiciona a qualquer variável, ou seja descarta-o.

No entanto isto não e muito eficaz. por vezes perdem-se caráteres. Irei continuar a procura de outras soluções. Mas se alguém souber uma agradecia....

o scanf também dá para ler espaços. não saber como se faz não é resposta da impossibilidade de não o fazer:

 char line[256] = {0};

 printf(">> ");
 fflush(stdout);
 scanf("%256[^\n]", line);
 printf("readed : %s", line);

Sim eu é que não sabia como fazer.

Edited by Leudassdf

Share this post


Link to post
Share on other sites
HappyHippyHippo

Hoje descobri mais uma solução que permite " limpar" o input do "\n"

scanf(...);
while (getchar() != '\n') /* void */;


IRC : sim, é algo que ainda existe >> #p@p

Share this post


Link to post
Share on other sites
Leudassdf

scanf(...);
while (getchar() != '\n') /* void */;

Mais uma. Obrigado

Cumprimentos

Leandro

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.