Não imprime char [RESOLVIDO]

1. Não imprime char [RESOLVIDO]

Patrícia de Azeredo Quintão
patriciaquintao

(usa Outra)

Enviado em 24/03/2018 - 13:40h

Pessoal,
Estou com um problema que acredito que seja o compilador mas não entendo, será que alguém já passou por isso?

A linha que eu sinalizei abaixo a variável não é impressa, seria alguma configuração do compilador? Eu testei com o DevC e o CodeBlokes e deram o mesmo problema, só funcionou com o TURBO C.

O estranho é que se eu retirar a variável sexo o nome funciona normal.

#include<stdio.h>
main ()
{
char nome[30],sexo;
int idade=0;
printf ("Digite seu nome: ");
scanf ("%s",&nome);
printf ("Digite sua idade: ");
scanf ("%d",&idade);
printf ("Digite seu sexo m ou f: ");
scanf ("%s",&sexo);
printf ("Nome digitado: %s",nome); // nessa linha a variável não está sendo impressa.
}



  


2. MELHOR RESPOSTA

Hugo Cerqueira
hrcerq

(usa Outra)

Enviado em 24/03/2018 - 15:02h

Olá.

Primeiramente, vamos resolver a terminologia: a rigor não podemos dizer que DevC, CodeBlocks e TurboC são compiladores. Eles na verdade são IDEs que usam um compilador internamente. Inclusive, um mesmo compilador pode ser usado em IDEs diferentes, e uma mesma IDE pode também trabalhar com mais de um compilador, deixando a escolha de qual usar a seu critério (caso do CodeBlocks).

Segundamente, vamos analisar o problema em questão: observe o seguinte trecho do seu programa:

printf ("Digite seu nome: ");
scanf ("%s",&nome);


Perceba que a variável "nome" é um vetor de char. Essencialmente um vetor é um bloco de memória reservado para uma sequência de valores de um tipo determinado (char nesse caso), sendo que cada elemento será identificado pela sua posição. Cada char corresponde a um byte, então esse bloco de memória (nome) é segmentado em bytes. Quando você digita nome[0] por exemplo, está dizendo que quer ler o valor do primeiro byte desse bloco, nome[1] o segundo e assim por diante.

Não sei se você conhece o conceito de ponteiros, mas esse conceito é essencial para entender o problema: um ponteiro é uma variável que aponta para um endereço de memória onde estará (ou espera-se que esteja) guardado algum valor que você vai usar. Quando você digita &nome[0] está se referindo ao endereço de memória da primeira posição do vetor. Porém, ocorre a expressão "nome" significa exatamente a mesma coisa que "&nome[0]", isto é, um ponteiro para a primeira posição do vetor. E é exatamente isso que a função scanf está esperando, um ponteiro para a primeira posição da string (já que o primeiro parâmetro é "%s" e não "%c"). Porém, se você coloca o segundo parâmetro como "&nome", então está informando um ponteiro para outro ponteiro. Por isso a escrita não ocorre como esperado.

Assim, a segunda linha deve ser corrigida para:

scanf("%s",nome); 


Existe um segundo problema a ser corrigido, observe o seguinte trecho de código do seu programa:

printf ("Digite seu sexo m ou f: ");
scanf ("%s",&sexo);


Diferente da variável "nome", a variável "sexo" não é um vetor. E ainda assim, está sendo tratado como string (que em C nada mais é que um vetor de char). Nesse caso, deve substituir o parâmetro "%s" por "%c". Note que se fizer apenas isso, a escrita ainda assim não vai funcionar. Isso ocorre porque pode haver algum caracter de fim de linha no buffer de entrada (stdin), que vai imediatamente ser escrito na variável e vai pular a etapa de entrada do usuário. Você pode contornar esse problema adicionando um espaço antes de "%c", assim o código ficaria assim:

scanf(" %c",&sexo); 


Observe que o "e comercial" (&) foi mantido nesse caso, pois dessa vez deve-se passar um ponteiro para a variável sexo, que não é um vetor e sim apenas um char.

Terceiramente, vamos tentar entender porque o programa funcionou como esperado (por você) quando compilado no Turbo C (pelo compilador interno dele, o qual não conheço). O fato é que da maneira como o código está, o normal seria não funcionar mesmo. Talvez o compilador do Turbo C tenha alguma regra que identifique esse erro (que é bem comum) e já aplique a correção (não tenho como confirmar se é isso mesmo que ocorre). Pessoalmente não acho que isso seja bom, porque induz o programador a continuar errando. De toda forma, o Turbo C já está descontinuado há algum tempo, assim como o DevC.

---

Atenciosamente,
Hugo Cerqueira

3. Re: Não imprime char

Paulo Jr
Pebis

(usa Debian)

Enviado em 24/03/2018 - 13:53h

patriciaquintao escreveu:

Pessoal,
Estou com um problema que acredito que seja o compilador mas não entendo, será que alguém já passou por isso?

A linha que eu sinalizei abaixo a variável não é impressa, seria alguma configuração do compilador? Eu testei com o DevC e o CodeBlokes e deram o mesmo problema, só funcionou com o TURBO C.

O estranho é que se eu retirar a variável sexo o nome funciona normal.

#include<stdio.h>
main ()
{
char nome[30],sexo;
int idade=0;
printf ("Digite seu nome: ");
scanf ("%s",&nome);
printf ("Digite sua idade: ");
scanf ("%d",&idade);
printf ("Digite seu sexo m ou f: ");
scanf ("%s",&sexo);
printf ("Nome digitado: %s",nome); // nessa linha a variável não está sendo impressa.
}


scanf ("%s",&nome); < nesta linha tinha o &
scanf ("%s",&sexo); < nesta linha tinha o &


4. Re: Não imprime char [RESOLVIDO]

Patrícia de Azeredo Quintão
patriciaquintao

(usa Outra)

Enviado em 24/03/2018 - 14:00h

Não é para ter o & no scanf de char?

Não entendo que se eu tirar a variável sexo o nome funciona normal, mesmo com o &


5. Re: Não imprime char [RESOLVIDO]

Paulo Jr
Pebis

(usa Debian)

Enviado em 24/03/2018 - 14:02h

Tirei aqui com gcc e funcionou. Deve ser de compilador para compilador.


6. Re: Não imprime char [RESOLVIDO]

Patrícia de Azeredo Quintão
patriciaquintao

(usa Outra)

Enviado em 24/03/2018 - 14:10h

Tirei aqui com o Devc e não adiantou. Qual foi o compilador que usou? Tem o link de download?


7. Re: Não imprime char [RESOLVIDO]

Patrícia de Azeredo Quintão
patriciaquintao

(usa Outra)

Enviado em 24/03/2018 - 23:04h

Nossa! Muito bom! Pro visto preciso ler mais na teoria. Só não entendi o motivo de dar um espaço antes do %c. Por acaso vc me indica algum livro ou texto sobre o assunto?


8. Re: Não imprime char [RESOLVIDO]

Hugo Cerqueira
hrcerq

(usa Outra)

Enviado em 25/03/2018 - 16:40h

patriciaquintao escreveu:

Nossa! Muito bom! Pro visto preciso ler mais na teoria. Só não entendi o motivo de dar um espaço antes do %c. Por acaso vc me indica algum livro ou texto sobre o assunto?


Acho que minha explicação do espaço antes do %c realmente não ficou clara. Mas é que funciona assim: a função scanf recebe os valores digitados no teclado e guarda em uma variável (na memória). Essa transmissão não é direta, os valores passam primeiro por um buffer, que é a entrada padrão (chamada de stdin, ou seja, standard input). Então você digita no teclado, o valor é guardado em stdin e em seguida transferido para a variável que você criou.

Só que quando tem algum "lixo" no buffer (um valor que ficou lá e que você não vai usar), quando você faz a leitura de um caracter, o buffer vai transferir esse "lixo" que estava lá antes e assim a leitura do teclado é ignorada. Quando você coloca um espaço antes de %c está informando à função scanf que não quer ler qualquer caracter de fim de linha que tenha ficado no buffer.

Sobre indicação de livro, tenho sim. Só espero que não tenha problemas com a língua inglesa porque achar material bom e gratuito em português não é fácil. Segue uma referência que acho muito bem estruturada e didática:

http://www-personal.acfr.usyd.edu.au/tbailey/ctext/

Nesse site o autor disponibiliza o livro (PDF) e alguns códigos para você praticar. O livro é um pouco antigo, mas tem o principal que você precisa saber sobre C. Além disso ele é um livro para uma leitura mais longa. Se quiser algo mais rápido para começar (mas mesmo assim ainda continuo recomendando o livro), existem alguns tutoriais mais simples (e visuais):

http://www.zentut.com/c-tutorial/
https://www.tutorialspoint.com/cprogramming/index.htm
https://www.tutorialspoint.com/c_standard_library/index.htm

---

Atenciosamente,
Hugo Cerqueira


9. Re: Não imprime char

Paulo
paulo1205

(usa Ubuntu)

Enviado em 26/03/2018 - 17:41h

hrcerq escreveu:

Primeiramente, vamos resolver a terminologia [...]

Não sei se você conhece o conceito de ponteiros, mas esse conceito é essencial para entender o problema: um ponteiro é uma variável que aponta para um endereço de memória onde estará (ou espera-se que esteja) guardado algum valor que você vai usar.


Como o objetivo é esclarecer a terminologia, permita-me alguns pequenos apartes.

Um ponteiro é um valor que indica um endereço de memória (e que também, durante a compilação, pode trazer informação sobre o tipo de dado que pode estar guardado nesse endereço). Esse valor de endereço pode existir independentemente de se existe ou não uma variável que o armazene.

Dizer que uma variável “é” um ponteiro é comum, mas é impreciso — tão impreciso, na verdade, quanto dizer que uma variável “é” um inteiro, ou um double ou qualquer outro tipo de dado. Em todos esses casos, o que se quer realmente dizer é que os valores que tais variáveis permitem guardar são do tipo X ou Y.

Quando você digita &nome[0] está se referindo ao endereço de memória da primeira posição do vetor. Porém, ocorre a expressão "nome" significa exatamente a mesma coisa que "&nome[0]", isto é, um ponteiro para a primeira posição do vetor.


Na verdade, depende do contexto.

O que você disse é quase sempre verdade, mas há dois casos em que as duas expressões são diferentes: quando o nome do array é usado como operando dos operadores sizeof ou &. Em todas as demais expressões, o nome do array produz, por decaimento, um valor que funciona como ponteiro para seu primeiro elemento.

E é exatamente isso que a função scanf está esperando, um ponteiro para a primeira posição da string (já que o primeiro parâmetro é "%s" e não "%c").


Eu concordo com você, mas acho importante deixar claro que é do programador o ônus de garantir que o ponteiro para caráter se refere ao primeiro elemento de um array, em vez de a um único caráter. A função scanf() — e, aliás, qualquer função em C — não tem como qualificar o dado original ao qual um ponteiro se refere, porque toda passagem de argumentos funciona com cópia de valores. De posse apenas do valor, e não da expressão de onde ele saiu, não há meios de desfazer um possível decaimento de array para ponteiro.

Porém, se você coloca o segundo parâmetro como "&nome", então está informando um ponteiro para outro ponteiro. Por isso a escrita não ocorre como esperado.


Correto!

Contudo, nem sempre esse erro aparece facilmente pois, embora os tipos de dados sejam distintos (nome decai para “ponteiro para char”; &nome é calculado (não decai!) e é do tipo “ponteiro para array com 30 elementos do tipo char”), os valores numéricos dos dois ponteiros costuma ser o mesmo, ou seja, o endereço bruto, desconsiderando os tipos de cada um, é idêntico para ambos. Além disso, funções com número variável de argumentos, como printf() e scanf(), têm regras que tornam o diagnóstico desse tipo de erro ainda mais complicado.

Mesmo assim, por causa da prevalência desse tipo de erro em operações comuns de entrada e saída, alguns compiladores implementam testes específicos para fazer uma validação básica entre as strings de formatação dessas funções e os tipos dos demais argumentos. No caso do gcc, que é o compilador padrão da maioria das distribuições de Linux, é necessário aplicar opções de compilação que habilitem tal diagnóstico. Pode-se usar, para tanto, “-Wformat” ou “-Wall”. Eu recomendo sempre “-Wall”, bem como “-Werror”, para que o compilador, além de alertar sobre código perigoso, impeça a compilação de seguir adiante se tal código perigoso for encontrado.

Note porém que esses diagnósticos apresentam apenas garantias necessárias (quanto ao tipo de dados dos argumentos), não garantias suficientes (e.g. um ponteiro para char será válido tanto para conversão com "%s" quanto para conversão com "%c", seja ele gerado a partir do decaimento de um array, seja calculado, seja reencaminhado de alguma variável de tipo ponteiro).

Terceiramente, vamos tentar entender porque o programa funcionou como esperado (por você) quando compilado no Turbo C (pelo compilador interno dele, o qual não conheço). O fato é que da maneira como o código está, o normal seria não funcionar mesmo. Talvez o compilador do Turbo C tenha alguma regra que identifique esse erro (que é bem comum) e já aplique a correção (não tenho como confirmar se é isso mesmo que ocorre). Pessoalmente não acho que isso seja bom, porque induz o programador a continuar errando. De toda forma, o Turbo C já está descontinuado há algum tempo, assim como o DevC.


Não é proteção do Turbo C, mas deve ter sido uma daquelas coincidências fortuitas devido à forma como as variáveis locais são dispostas na memória.

Em sua explicação sobre o porquê de usar conversão "%c" quando o destino é um único caráter, faltou você dizer duas coisas: (1) por que não foi necessário colocar o espaço antes do especificados de conversão "%s", e (2) o que acontece quando a pessoa trata (no caso, fazendo com que scanf() trate) um único caráter como se fosse um array.

A resposta de (1) é que a função scanf() automaticamente executa o descarte de espaços em branco que ocorram antes de determinadas conversões, incluindo "%s", mas não quando se usa "%c".

No caso de (2), o comportamento da função é tratar as posições de memória adjacentes ao único caráter como se fossem partes de um único array. As consequências últimas de assim se proceder são imprevisíveis, mas o que geralmente ocorre inicialmente é que os dados provenientes da conversão sobrescrevem variáveis declaradas no entorno da variável usada de modo incorreto. Como toda string em C tem de ter um byte nulo após o final dos caracteres que a compõem, mesmo que o usuário digite apenas um caráter, a função scanf() vai guardar dois bytes: o primeiro (digitado pelo usuário) no lugar esperado, e o segundo (o byte nulo) na posição de memória seguinte, pertença ela a quem pertencer.

O padrão do C não prescreve o modo como variáveis devem ser dispostas na memória, de modo que cada compilador pode fazer suas próprias escolhas, inclusive levando em conta situações como uso ou não de otimizações e situações especiais de alinhamento de dados. Pelas descrições de comportamento no DevC++ e Code::Blocks, que usam o GCC como compilador, e no Turbo C, uma explicação possível seria que o GCC dispôs em memória primeiro sexo, e depois, bem adjacente, nome, de modo que o byte nulo gravado pela leitura de sexo com uma conversão de string acabou sobrescrevendo a primeira posição de nome, tornando-a, para todos os efeitos, uma string vazia. O Turbo C, pelo visto, usou uma disposição de dados diferente, de modo que o byte nulo após sexo não chegou a sobrescrever o conteúdo de nome (mas pode eventualmente ter perturbado o valor de idade, cujo valor não foi impresso para comparação).


10. Re: Não imprime char

Paulo
paulo1205

(usa Ubuntu)

Enviado em 27/03/2018 - 01:32h

hrcerq escreveu:

Acho que minha explicação do espaço antes do %c realmente não ficou clara. Mas é que funciona assim: a função scanf recebe os valores digitados no teclado e guarda em uma variável (na memória). Essa transmissão não é direta, os valores passam primeiro por um buffer, que é a entrada padrão (chamada de stdin, ou seja, standard input). Então você digita no teclado, o valor é guardado em stdin e em seguida transferido para a variável que você criou.


A explicação acima tem algumas imprecisões:

  • Falar em “digitados” e “teclado” é particularizar uma descrição de um comportamento muito mais genérico. Na verdade, o padrão da biblioteca do C fala em termos de um fluxo de entrada de dados padrão (standard input stream), o qual, num computador como os nossos PCs, pode estar, sim, ligado ao teclado, mas também pode, com muita facilidade, ser alterado para um arquivo ou para um canal de comunicação com outro processo ou outro computador.

  • Nem sempre scanf() é usada para guardar valores em variáveis. Na verdade, a função serve para procurar (scan) dados formatados (f) de acordo com uma regra de formação (a string de formatação). Alguns dos componentes da string de formatação podem ser usados também para indicar à função que copie partes dos dados analisados para outras variáveis, mas isso não é obrigatório.

  • A entrada padrão não é, do ponto de vista do C, um buffer (ainda que o seja para o sistema operacional). O que ela é é um fluxo (stream), e o fluxo pode ter um buffer a ele associado, mas não necessariamente.

A verdade é que scanf() é uma função bastante complexa, possivelmente a mais complexa da biblioteca padrão do C. De certo modo, é uma infelicidade que algo tão complexo acabe aparecendo tão cedo para programadores novatos. Contudo, mais infeliz ainda é que o funcionamento dessa função seja mal ensinado nos cursos introdutórios ao C ou a programação em geral.

E eu não acho que esse ensino precário se justifique, uma vez que existe documentação excelente sobre ela em vários lugares. Só como exemplo, a manpage sobre scanf() que vem com o Linux é primorosa — e gratuita!

Só que quando tem algum "lixo" no buffer (um valor que ficou lá e que você não vai usar), quando você faz a leitura de um caracter, o buffer vai transferir esse "lixo" que estava lá antes e assim a leitura do teclado é ignorada. Quando você coloca um espaço antes de %c está informando à função scanf que não quer ler qualquer caracter de fim de linha que tenha ficado no buffer.


A expressão de “lixo no buffer” é outra que considero imprópria. Por que “lixo”? Só porque o programador não vai usá-lo, não quer dizer que seja inútil. Se aquele suposto lixo funciona, na verdade como delimitador dos dados, e é essencial para algumas conversões de scanf(), tais como "%d" ou, muito especialmente, para "%s", não é realmente “lixo”.

Se o programador não está interessado no delimitador, então que tome as providências necessárias para o identificar e remover. Não faltam recursos para isso em scanf(), por exemplo. Só que, para usar tais recursos, é necessário conhecer a função.


11. Re: Não imprime char [RESOLVIDO]

Hugo Cerqueira
hrcerq

(usa Outra)

Enviado em 27/03/2018 - 11:34h

Muito obrigado, Paulo, mais uma vez, pelas suas considerações sobre as minhas considerações. Ainda pretendo chegar nesse nível de precisão de informação. Prometo que ficarei mais atento. =)

---

Atenciosamente,
Hugo Cerqueira


12. Re: Não imprime char

Paulo
paulo1205

(usa Ubuntu)

Enviado em 27/03/2018 - 15:17h

Caro Hugo,

Por favor, não interprete como se eu estivesse criticando você. Meu objetivo é desfazer eventuais mal-entendidos — até porque, muitos desses pontos imprecisos o foram para mim também (e alguns até muito recentemente), e me fizeram passar muito mais tempo do que eu gostaria depurando funcionamento de coisas que eu realmente não entendia.

No mais, quanto mais interação todos tivermos, tanto maior tenderá a ser a propagação de conhecimento adequado.



01 02



Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts