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).