paulo1205
(usa Ubuntu)
Enviado em 04/12/2019 - 17:29h
Um bom ponto de partida é configurar o compilador para detectar o máximo de erros para você (deixe para ele a tarefa de caçar erros bobos, e concentre-se em resolver seu problema).
Você não disse qual compilador está usando, nem qual sistema. Com o GCC, eu recomendo sempre o seguinte conjunto de opções para diagnóstico de código:
-Wall -Werror -pedantic-errors -O2 . Com elas, compilando seu programa (que eu chamei de “prog.c” quando o coloquei na minha máquina), eis as mensagens de erro que a compilação produziu,
prog.c: In function ‘prencher’:
prog.c:25:21: error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[15]’ [-Werror=format=]
scanf("%s", &funcionario[num].cpf);
~^ ~~~~~~~~~~~~~~~~~~~~~
prog.c:18:19: error: unused variable ‘media’ [-Werror=unused-variable]
float media;
^~~~~
prog.c:17:27: error: unused variable ‘ano’ [-Werror=unused-variable]
int dia, mes, ano;
^~~
prog.c:17:22: error: unused variable ‘mes’ [-Werror=unused-variable]
int dia, mes, ano;
^~~
prog.c:17:17: error: unused variable ‘dia’ [-Werror=unused-variable]
int dia, mes, ano;
^~~
prog.c:16:19: error: unused variable ‘qtd’ [-Werror=unused-variable]
int i,qtd;
^~~
prog.c:16:17: error: unused variable ‘i’ [-Werror=unused-variable]
int i,qtd;
^
prog.c:15:28: error: unused variable ‘u’ [-Werror=unused-variable]
int num =0,p=0,u=0;
^
prog.c: In function ‘imprimir’:
prog.c:56:7: error: ‘n’ undeclared (first use in this function)
*num= n;
^
prog.c:56:7: note: each undeclared identifier is reported only once for each function it appears in
prog.c:60:12: error: ‘i’ undeclared (first use in this function)
for(i=0;i<n;i++){
^
prog.c: In function ‘main’:
prog.c:77:5: error: implicit declaration of function ‘preencher’; did you mean ‘prencher’? [-Wimplicit-function-declaration]
preencher(&funcionario);
^~~~~~~~~
prencher
prog.c:80:5: error: too few arguments to function ‘imprimir’
imprimir(&funcionario);
^~~~~~~~
prog.c:54:6: note: declared here
void imprimir(registro_funcionario *funcionario, int *num){
^~~~~~~~
prog.c:72:7: error: unused variable ‘media’ [-Werror=unused-variable]
float media;
^~~~~
prog.c:70:15: error: unused variable ‘count’ [-Werror=unused-variable]
int num=0,i,n,count;
^~~~~
prog.c:70:13: error: unused variable ‘n’ [-Werror=unused-variable]
int num=0,i,n,count;
^
prog.c:70:11: error: unused variable ‘i’ [-Werror=unused-variable]
int num=0,i,n,count;
^
prog.c:70:5: error: unused variable ‘num’ [-Werror=unused-variable]
int num=0,i,n,count;
^~~
prog.c: In function ‘prencher’:
prog.c:25:13: error: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Werror=unused-result]
scanf("%s", &funcionario[num].cpf);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
Mesmo que você desconsidere as mensagens sobre doze variáveis declaradas mas não usadas, ainda há outros seis problemas para corrigir, alguns mais graves do que outros.
O erro da linha 25 acontece porque você está passando um argumento para
scanf () cujo tipo não é compatível com a conversão especificada com
%s , para leitura de
strings . Ao usar o operador
& antes do nome do vetor de caracteres indicado por
funcionario[num].cpf , você produziu um ponteiro para vetores de caracteres com quinze elementos cada um, não um ponteiro para caracteres, que é o que a conversão esperaria receber. Tire aquele
& dali, e esse erro vai se consertar, porque vetores de caracteres, como o campo
cpf da estrutura
registro_funcionario , são automaticamente convertidos em ponteiros para o primeiro elemento na maioria dos contextos de uso (isto é: após o momento de sua declaração), com apenas duas exceções: quando usado como argumento do operadores
& (seu caso) ou do operador
sizeof .
(A conversão implícita de
arrays em ponteiros é um aspecto essencial da linguagem C, mas é uma coisa que eu nunca vi ser bem explicada em curso nenhum ou em livro de que eu tenha conhecimento. E é uma coisa que meio que depende de uma abordagem
bottom-up de ensino de programação)
Essa mesma linha tem outro problema, que é o fato de você ignorar o valor de retorno de
scanf (). Para programas seguros, isso nunca deve acontecer, porque eventuais erros de leitura ou falhas em obedecer completamente a
string de formatação podem, por um lado, deixar as variáveis que deveriam receber os valores lidos/convertidos com valores indefinidos, de modo que decisões tomadas com base nesses valores deixaria de fazer sentido, e, por outro lado, pode deixar o
buffer de entrada de dados num estado inconsistente que comprometerá operações de leituras subsequentes.
Outro erro que aparece, que cai na categoria de erros bobos que o compilador, às vezes, é melhor que nós para pegar, é a discrepância entre o nome
prencher , que você usou para declarar a função, e o nome (correto, em termos de Português)
preencher , que usou na hora de a chamar.
Mais grave, no entanto, é o erro da linha 80, em que você chama
imprimir (), que é uma função que tem dois parâmetros, com apenas um argumento, deixando o segundo parâmetro sem um argumento correspondente. Quando corrigir isso, tenha certeza de que o tipo do segundo parâmetro faz sentido. Não me parece que o tipo que você usou seja muito adequado.
Outros erros têm a ver com variáveis usadas sem terem sido declaradas. Você inclusive se queixou a respeito disso, mas talvez porque você se confundiu ou não foi corretamente ensinado a respeito de variáveis locais, cuja visibilidade é limitada ao bloco de código no qual suas declarações aparecem. Se você quiser que um valor produzido dentro de uma função seja enxergado por outra função, deve usar variáveis globais (solução nem sempre muito elegante) ou deve usar alguma maneira de passar os valores de dentro da função para fora dela, quer por meio do valor de retorno da função, quer através de ponteiros, que a função usa internamente para ter acesso de leitura ou de leitura e escrita aos objetos apontados.
E isso nos traz a outro erro no seu programa, e um que o compilador não consegue apontar para você, meso com as opções de diagnóstico que usamos acima. Ao usar ponteiros para permitir que uma função altere um objeto externo, é necessário usá-los de um modo correto, e isso não aconteceu na sua função
preencher ().
Eis outra característica essencial do C (e que também raramente é bem explicada em cursos introdutórios): todos os parâmetros de função recebem sempre cópias dos valores dos argumentos usados no momento em que a função é invocada (isso normalmente é denominado de “chamada por valor”, ou “
call by value ”). Quando, dentro de
main (), você chama “
preencher(&funcionario) ”, o compilador obtém o valor do endereço de
funcionario e o copia para a uma região de memória onde ficam os argumentos da função, e então a chama. Dentro da função, o valor copiado passa a ser referido pelo nome local
funcionario , declarado na lista de parâmetros, mas que, no corpo da função, se comporta como qualquer outra variável local da função. Note que, ao usar essa variável local, você apenas altera o seu valor (inicialmente copiado a partir do argumento), em vez de usar o valor original para chegar ao objeto que reside no endereço para o qual o ponteiro aponta (com a sintaxe “
* nome_do_ponteiro”) e, com isso, poder modificar esse objeto.
Contudo, só colocar um asterisco antes de cada referência ao ponteiro dentro da função não vai funcionar para você. Quando você declarou
funcionario dentro de
main (), você pediu para usar alocação
automática de memória. Dentro de
preencher (), no entanto, você parece querer trabalhar com alocação
dinâmica , que é algo diferente(†). A maneira usual de trabalhar com alocação dinâmica é mais ou menos a seguinte.
typedef /* alguma definição */ meu_tipo;
meu_tipo *funcao_de_alocacao(/* parâmetros necessários */){
meu_tipo ponteiro_local; /* esse ponteiro será alocado e inicializado de acordo com os eventuais argumentos. */
/* Algum código, possivelmente usando malloc()/calloc()/realloc() e/ou obtenção de outros recursos. */
return ponteiro_local;
}
void funcao_de_liberacao(meu_tipo *ponteiro_a_ser_liberado){
/* Se algum outro recurso, além de memória, tiver sido usado, libera tais recursos antes de liberar a memória. */
/* Libera a memória, possivelmente chamado free(). */
}
void funcao_de_manipulacao_sem_realocacao(meu_tipo *ponteiro /*, outros eventuais argumentos necessários para manipulação */){
/*
Manipula os objetos, podendo alterar seus valores, mas não os locais ou os tamanhos alocados, usando
referências nas formas “*ponteiro” ou “ponteiro[indice]”.
*/
/* Você pode usar um tipo de retorno diferente de void para sinalizar sucesso/erro através do valor de retorno. */
}
void funcao_de_manipulacao_com_realocacao(meu_tipo **p_ponteiro /*, outros argumentos necessários para a manipulação */){
/* Realoca o “*p_ponteiro”, não “p_ponteiro”. */
/* A eventual manipulação de elementos é feita com “**p_ponteiro” ou “(*p_ponteiro)[indice]”.
}
void funcao_de_consulta(const meu_tipo *ponteiro /*, outros argumentos que qualificam a consulta */){
/* Note, acima, o uso de const, que indica que os dados apontados não podem ser alterados. */
}
int main(void){
meu_tipo *p_objeto; /* Note que eu declaro um ponteiro. */
/* ... */
p_objeto=funcao_de_alocacao(/* argumentos */);
/* ... */
if(alguma_condicao)
funcao_de_manipulacao_com_realocacao(&p_objeto /*, argumentos */); /* Note o uso de &. */
else if(outra_condicao)
funcao_de_manipulacao_sem_realocacao(p_objeto /*, argumentos */);
else if(ainda_outra_condicao)
funcao_de_consulta(p_objeto /*, argumentos */);
/* ... */
funcao_de_desalocao(p_objeto);
}
E outra coisa ainda (e que, de novo, eu raramente vejo cursos introdutórios explicarem direito): a forma como você usou
realloc (), embora não seja rigorosamente errada (e por isso o compilador não reclamou), é problemática. Você está usando a mesma variável ponteiro como argumento e como destino do valor de retorno. O problema com isso é que a realocação eventualmente pode falhar. Quando falha, a função garante que a memória apontada pelo ponteiro original é preservada, mas ela retorna um ponteiro nulo para indicar o erro. Ao sobrescrever o ponteiro com o valor nulo, você perde a referência à memória original, embora o conteúdo esteja intacto.
A forma certa de fazer a realocação é a seguinte.
void *p_novo; /* “void *” é automaticamente conversível de/para qualquer outro tipo de ponteiro em C (mas não em C++!). */
p_novo=realloc(p_original, novo_tamanho);
if(!p_novo){
/*
Erro de realocação. Seu programa pode optar por seguir com o
valor original do ponteiro e com o conteúdo anterior preservado, ou
pode preferir abortar a execução.
*/
}
else{
/* Alocação bem-sucedida. Sobrescreve o ponteiro original com o obtido na realocação. */
p_original=p_novo;
}
... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)
-----
† Relembrando os tipos de alocação de memória em C:
estática significa que o endereço para a variável é plenamente determinado desde o momento da compilação;
automática : significa que o endereço exato é determinado durante a execução, mas a quantidade de memória a ser alocada é bem conhecida durante o tempo da compilação, e o compilador gera código para calcular os endereços necessários para tal quantidade de memória automaticamente;
dinâmica significa que a alocação tem de se feita manualmente pelo programa em tempo de execução, porque o programa não tem como saber de antemão quanta memória será necessária, nem quando, nem quais endereços usar para acomodar tal quantidade de dados, de modo que nem mesmo pode associar diretamente uma variável para indicar tais objetos, mas o programador tem de ponteiros para referenciar a memória que eventualmente conseguir obter.