Struct, Ponteiros, Malloc/Realloc [RESOLVIDO]

1. Struct, Ponteiros, Malloc/Realloc [RESOLVIDO]

João Paulo
princknoby

(usa Arch Linux)

Enviado em 11/01/2021 - 13:30h

Olá,

Estou enfrentando um grande "problema", estou procurando onde está o erro a dias e não consigo encontrar...

Tenho o seguinte código (estou apenas brincando, tentando entender alguns conceitos, não é nada para fins profissionais).

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

typedef struct dados {
char nome[50], CPF[30];
} Dados;

void _fflush();
void add(Dados *pd, int n);
void show(Dados *pd, int n);

int main(int argc, char *argv[]) {
int n = 0, op;
Dados *pd = malloc(n * sizeof(pd));
system("clear");

do {
printf("Digite uma opção: ");
scanf("%d", &op);
_fflush();
if (op == 1) {
add(&pd[n], n);
n++;
pd = realloc(pd, n * sizeof(pd));
}
else if (op == 2) { show(pd, n); }
} while (op != 0);

free(pd);
return 0;
}

void _fflush() {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}

void add (Dados *pd, int n) {
printf("Digite o nome: ");
scanf("%49[^\n]", pd->nome);
_fflush();

printf("Digite o CPF: ");
scanf("%29[^\n]", pd->CPF);
_fflush();
}

void show (Dados *pd, int n) {
for (unsigned int i = 0; i < n; i++) {
printf("%s\t%s\n", pd[i].nome, pd[i].CPF);
}
}


Meu objetivo é ser capaz de ler Nome e CPF de forma dinâmica (até que o usuária decida que não quer adicionar mais nenhuma pessoa), e ter a opção do programa de adicionar alguem (1), mostrar os nomes e cpf adicionados (2) e sair do programa (0)

Porém sempre o Primeiro Nome e CPF lido quando vou listá-los (opção 2), os 2 primeiros dígitos do CPF, SEMPRE são "engolidos" e nunca aparecem, vou tentar colocar um print aqui da saída.

E para minha surpresa, quando compilo e rodo o programa no seguinte site: https://www.onlinegdb.com/online_c_compiler o programa funciona como o esperado!

Enfim, não sei onde está o problema, não sei se essa informação é útil, mas minha versão do gcc é a seguinte: gcc version 10.2.0 (GCC)

Desde já, obrigado


  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 14/01/2021 - 22:24h

princknoby escreveu:

Obrigado pela atenção Paulo!

Acho que consegui entender a questão de declarar n = 0

Fiz o programa declarando n =1 :
int n = 1;
Dados *pd = malloc(n * sizeof *pd) // Não entendi essa questão muito bem... *pd e Dados tem o mesmo tamanho?!
// Eu deveria usar sizeof *pd ou sizeof(Dados)?


Para manter o programa coerente, seria interessante indicar que a quantidade n de elementos do array dinâmico é zero, até o momento em que você decidir incluir o primeiro elemento. E a forma de evitar o comportamento dúbio de malloc() invocado com tamanho zero é fazer tão somente o seguinte.
size_t n=0;  // Atenção ao fato de que tamanhos em C são medidos com o tipo size_t, não com int.
Dados *pd=NULL; // É seguro, depois, chamar realloc(p, n+1), mesmo que p seja inicialmente nulo.


Para alocações iniciais, eu prefiro, sempre que possível, usar uma das seguintes formas (que são absolutamente sinônimas, e nas quais p é um ponteiro para qualquer tipo diferente de void e N um valor inteiro positivo).
p=malloc(N * sizeof *p); 

p=malloc(N * sizeof p[0]); 


Qualquer dessas duas formas é vantajosa por reduzir a quantidade de coisas que você tem de escrever (a não ser que você comece a usar variáveis com nomes gigantescos e nomes de tipos bem curtinhos), e joga o trabalho de recuperar a informação sobre o tipo de dado apontado para o compilador, que já tem essa informação desde o momento em que o ponteiro foi declarado.

Se o tipo do ponteiro p for void * (ponteiro para tipo de dados não-determinado), a expressão *p é inválida (e também não faria sentido fazer “sizeof (void)”). Nesse caso, o tamanho de cada elemento ao qual o ponteiro se refere teria de ser indicado de alguma outra forma; se tal forma for através da aplicação de sizeof a alguma coisa, ou você usaria o nome do tipo (por exemplo: “sizeof (double)”) ou indicaria algum outro ponteiro para dado de tipo conhecido como referência (assim como eu fiz, na postagem anterior, ao indicar a forma correta de chamar realloc()).

Infelizmente não dá para evitar repetição de p como destino do valor devolvido por malloc() e com indicador do elemento cujo tamanho deve ser calculado dor sizeof. No entanto, se essa repetição o incomodar, você pode criar macros de alocação e realocação (as macros são feias, mas o uso delas, mais tarde, pode vir a ser conveniente).
// Macros para alocação inicial: n pode ser constante ou variável, valor de p é alterado, e é o valor devolvido pela macro.
#define PTR_ALLOC(p, n) ( (p)=malloc((n) * sizeof *(p)) )
#define PTR_CALLOC(p, n) ( (p)=calloc((n), sizeof *(p)) )

// Função auxiliar para realocação genérica. Retorna verdadeiro se a alocação tiver sido bem sucedida e altera tanto o ponteiro quanto o tamanho.
// Se a realocação falhar, retorna falso e não altera nem o ponteiro nem o tamanho.
int _my_realloc(void **p, size_t *p_n, size_t new_n, size_t elem_size){
void *new_p=realloc(*p, new_n*elem_size);
if(new_p){
*p=new_p;
*p_n=new_n;
return 1;
}
return 0;
}

// Macro para realocação genérica. O argumento referente ao parâmetro n tem de ser uma variável do tipo size_t, que será alterada
// juntamente com o ponteiro p se a realocação tiver funcionado. Retorna um valor booleano indicando se a realocação foi bem sucedida.
#define PTR_REALLOC(p, n, new_n) _my_realloc((void **)&(p), &(n), new_n, sizeof *(p))


/* EXEMPLOS DE USO */
void func(...){
char *pc;
double *pd;
int *pi;
struct tm *ptm=NULL;
size_t ni, ntm=0;

if(PTR_ALLOC(pc, 6))
strcpy(pc, "Paulo");

if(!PTR_CALLOC(pd, 50)){
perror("Alocação de 50 doubles com valores iniciais zerados falhou");
exit(1);
}

ni=PTR_ALLOC(pi, 10)? 10: 0;

// Como ptm é incialmente NULL, a realocação tem o mesmo efeito de uma alocação inicial.
if(PTR_REALLOC(ptm, ntm, 25)){
if(!PTR_realloc(pi, ni, ntm)){
perror("Não foi possível realocar inteiros com a mesma quantidade de struct tms");
exit(2);
}
}

/* ... */
}


E na chamada da função para adicionar os dados, eu chamo a função assim:
add(&pd[n-1]); 

Tem alguma forma mais bonitinha ao invés de usar [n-1]?


Olha, o n-1 possivelmente vai aparecer mais cedo ou mais tarde de qualquer maneira: uma vez que o C começa a contar os índices de arrays com 0, os índices de um array com N elementos vão variar de 0 a N-1 (por exemplo: num array com cinco elementos, os índices válidos serão 0, 1, 2, 3 e 4). Em outras palavras, o valor absoluto do último índice válido será sempre uma unidade menor do que o valor absoluto do número de total de elementos.

Eu cheguei a sugerir, na mensagem anterior, que você alterasse a ordem entre a realocação e a adição do elemento. Se o fizer, possivelmente ficará com algo parecido com o seguinte.
  void *new_ptr=realloc(p, n+1);
if(!new_ptr){
perror("erro durante realocação");
exit(1);
}
p=new_ptr;
add(p+n++); // Sinônimo de “add(&p[n++]);”, e ambos têm o mesmo efeito de “add(p+n); ++n;”.


Eu pensei em passar todos os elementos da struct e usar uma variável de controle dentro da própria função, o problema foi que não consegui passar todos os elementos, fiquei confuso entre:
add(&pd); e add(pd); 


O problema é que só com a informação do ponteiro, você não sabe quantos elementos estão alocados, nem quantos estão em uso. De alguma forma, você teria de passar à função o valor de n.

Do jeito como você fez originalmente, o crescimento do array é feito fora da função add(), e você passa para ela o ponteiro para o elemento que acabou de ser acrescentado ao final. Apesar do nome, a função add() na verdade não adiciona nada, mas apenas preenche os valores do que já foi adicionado ao array fora da função, antes de ela ser chamada. Como você bem pode ver, o segundo parâmetro (n) acaba ficando sem uso dentro do corpo da função. (Por isso mesmo, no meu último exemplo, acima, eu omiti o segundo argumento: tendo visto como a função foi implementada, eu acabei ignorando mentalmente o segundo argumento.)

Você poderia passar a usar o segundo parâmetro, de modo a não precisar passar p+n como primeiro argumento. Nesse caso, você teria de mexer também no interior da função, passando a usar pd[n].nome e pd[n].CPF em lugar de pd->nome e pd->CPF, respectivamente, como no exemplo abaixo (reduzido para mostrar apenas as partes que interessam).
// Primeira opção: add() recebe o índice do elemento que será alterado.
void add(Dados *pd, size_t n){
int l;

// É importante sempre testar o grau de sucesso da leitura!
if(l=0, scanf("%49[^\n]%*1[\n]%n", pd[n].nome, &l)!=1 || l==0){
perror("dados inválidos para o nome");
exit(1);
}
if(l=0, scanf("%29[^\n]%*1[\n]%n", pd[n].CPF, &l)!=1 || l==0){
perror("dados inválidos para o CPF");
exit(1);
}
}

// MODO DE USO
int main(void){
size_t n=0;
Dados *pd=NULL;

/* ... */

PTR_REALLOC(pd, n, n+1);
add(pd, n-1);

/* ... */
}


Alternativamente, você poderia reescrever add() para receber p tamanho atual do array, e internamente fazer o cálculo de n-1.
// Segunda opção: add() recebe o tamanho do array (assim como show()), e calcula o índice para o último elemento a partir do tamanho.
void add(Dados *pd, size_t n){
if(n==0){
perror("não é possível ler elementos num array vazio");
exit(1);
}
--n; // Seguro de fazer, uma vez que o valor do parâmetro é uma mera cópia do valor do argumento. Agora n é o índice do último elemento, não mais o tamanho.
/* ... igual ao código anterior... */
}

int main(void){
Dados *pd=NULL;
size_t n=0;

/* ... */

PTR_REALLOC(pd, n, n+1);
add(pd, n);

/* ... */
}


Um passo além seria mover a realocação para dentro da função add(), fazendo com que o que ela faz tenha mais a ver com o seu nome. Nesse caso, a função teria de alterar tanto os valores de pd quanto de n, de modo que você teria de declarar a função com parâmetros que teriam de ser ponteiros para os valores a serem alterados (lembrando que C não tem passagem de argumentos por referência, mas apenas por cópia de valor, o que implica que você tem de criar explicitamente tais referências na hora de invocar a função).
// Opção 3: a função add() altera o array dinâmico e a informação do seu tamanho.
void add(Dados **pd, size_t *n){
void *new_ptr;
if(!(new_ptr=realloc(*pd, (1+*n) * sizeof **pd))){
perror("erro de realocação");
exit(1);
}
*pd=new_ptr;
int l;
if(l=0, scanf("%49[^\n]%*1[\n]%n", (*pd)[n].nome, &l)!=1 || l==0){
perror("dados inválidos para o nome");
exit(1);
}
if(l=0, scanf("%29[^\n]%*1[\n]%n", (*pd)[n].CPF, &l)!=1 || l==0){
perror("dados inválidos para o CPF");
exit(1);
}
++*n;
}

// MODO DE USO
int main(void){
Dados *pd=NULL;
size_t n=0;

/* ... */

// Agora você não faz realocação explícita aqui: ela é feita dentro de add().
// Por outro lado, note qe agora se tem “&pd” e “&n” em vez de “pd” e “n”.
add(&pd, &n);

/* ... */
}


Usando
add(pd); 
e recebendo na função:
void add (Dados *pd);  

Acabou que não funcionou e eu consegui adicionar dados apenas a primeira posição da struct :/


Pelo que você falou acima, pareceu-me que você gostaria de dar um passo além, e não ter de ficar passando duas variáveis independentes para as funções. De fato, essa falta de acoplamento entre o array e a representação do seu tamanho é inconveniente e é uma fonte comum de dificuldade de manutenção de código em programas grandes, e, por conseguinte, uma fonte em potencial de bugs.

Para resolver esse desacoplamento, você poderia juntar o ponteiro para o array dinâmico e o tamanho do array dentro de uma estrutura, e usar varáveis com esse tipo de estrutura para representar seus dados.
typedef struct {
size_t n;
struct { char nome[50], CPF[30]; } *pd;
} da_Dados; // “da_” de dynamic array.


De posse desse tipo de dados, agora você teria de criar funções para lidar com esse tipo de dados. Como ele tem campos que incluem alocação dinâmica de recursos (no caso, memória), você teria de ter, no mínimo, uma função de inicialização e outra de liberação de recursos (em idioma de orientação a objetos, tais funções seriam, respectivamente, um construtor e um destrutor).
void init_da_Dados(da_Dados *pda){
pda->n=0;
pda->pd=NULL;
}

void free_da_Dados(da_Dados *pda){
free(pda->pd);
pda->pd=NULL;
pda->n=0;
}

void show_n_da_Dados(const da_Dados *pda, size_t n){
if(n>=pda->n){
perror("índice fora da faixa");
exit(1);
}
printf("Nome: %s\nCPF: %s\n", pda->pd[n].nome, pda->pd[n].CPF);
}

void show_da_Dados(const da_Dados *pda){
if(pda->n){
printf("Nome: %s\nCPF: %s\n", pda->pd[0].nome, pda->pd[0].CPF);
for(size_t n=1; n<pda->n; ++n)
printf("\nNome: %s\nCPF: %s\n", pda->pd[n].nome, pda->pd[n].CPF);
}
}

void add_read_da_Dados(da_Dados *pda){
void *new_pd=realloc(pda->pd, (pda->n+1)*sizeof *pda->pd);
if(!new_pd){
perror("falha de realocação de memória");
exit(1);
}
int l;
if(l=0, scanf("%49[^\n]%*1[\n]%n", pda->pd[pda->n].nome, &l)!=1 || l==0){
perror("dados inválidos para o nome");
exit(1);
}
if(l=0, scanf("%29[^\n]%*1[\n]%n", pda->pd[pda->n].CPF, &l)!=1 || l==0){
perror("dados inválidos para o CPF");
exit(1);
}
++pda->n;
}

// MODO DE USO
int main(void){
da_Dados dad; // Declaração: aloca espaço para o objeto na memória automática.

init_da_Dados(&dad); // Inicializa (constrói) o objeto que foi alocado.

/* ... */

add_read_da_Dados(&dad); // Insere um elemento.

/* ... */

show_da_Dados(&dad); // Exibe todos os elementos.

/* ... */

free_da_Dados(&dad); // Libera recursos (desconstrói) o objeto quando ele não for mais necessário.
}


A maneira acima resolve quase todos os problemas de acoplamento, ao preço de mover quase todas as funcionalidades que operam sobre o array dinâmico para funções especializadas. Mas ainda existe um ponto do acoplamento que não foi resolvido: entre a criação do objeto (no momento de sua declaração) e sua inicialização (quando a função construtura é chamada), o objeto está num estado desconhecido (valores dos campos internos indeterminados). É até possível que o programador esqueça de chamar a função de inicialização completamente, o que pode trazer resultados imprevisíveis (pode funcionar hoje e não funcionar amanhã, ou pode funcionar num compilador, mas não com outro).

Além disso, note que todas as funções de manipulação do objeto, incluindo o construtor e o destrutor, empregam argumentos que são ponteiros, de modo que você é obrigado a colocar sempre um & na frente do argumento. Alguém poderia até ficar tentado a criar uma segunda variável com a forma de ponteiro, só para não ter de ficar escrevendo toda hora “&dad”. Ainda além disso, a alocação automática pode ter restrições de memória severas em alguns ambientes, de modo que seria preferível alocação totalmente dinâmica do objeto.

Finalmente, existe ainda uma questão de encapsulamento. Já que todas as manipulações no objeto são feitas por funções dedicadas, pode ser vantajoso esconder o que vai dentro do objeto, a fim proteger segredos comerciais (não neste exemplo trivial) e também evitar que os usuários desses objetos fiquem tentados a manipular diretamente seus campos internos, sem usar as funções especializadas.

Desse modo, uma outra possível evolução serviria para atacar todas as questões acima de uma só vez.

Se o construtor for ligeiramente reescrito, ele pode providenciar a criação do objeto diretamente na memória para alocação dinâmica e inicializar seu conteúdo, devolvendo um ponteiro para a área devidamente inicializada, eliminando qualquer possibilidade de trabalhar com um objeto com campos em estado indeterminado.
// Novo construtor: em vez de ter tipo de retorno “void”, retorna um ponteiro para a área criada e inicializada.
da_Dados *init_da_Dados(void){
da_Dados *new_pad=malloc(sizeof *new_pad);
if(!new_pad){
perror("falha de alocação de memória");
exit(1);
}
new_pad->n=0;
new_pad->pd=NULL;
return new_pad;
}

// O destrutor também tem de ser ajustado, a fim de liberar o ponteiro para o próprio objeto.
void free_da_Dados(da_Dados *pda){
free(pda->pd);
// As duas linhas abaixo não são estritamente necessárias.
pda->pd=NULL;
pda->n=0;

free(pda);
}


As demais funções não precisam sofrer alteração. Mas o modo de uso se altera ligeiramente, pois agora você vai usar um ponteiro para se referir ao objeto.
// MODO DE USO.

int main(void){
da_Dados *pdad=init_da_Dados(); // Aloca e inicializa (constrói) um novo objeto, referido através de um ponteiro.

/* ... */

add_read_da_Dados(pdad); // Insere um elemento.

/* ... */

show_da_Dados(pdad); // Exibe todos os elementos.

/* ... */

free_da_Dados(pdad); // Libera recursos (desconstrói) o objeto quando ele não for mais necessário.
}


Além disso, como não há mais a manipulação de nada fora das funções especializadas que não seja através de ponteiros, você pode esconder a implementação tanto dessas funções quanto da própria definição do tipo de dados da_Dados, isolando declarações e implementação.

Por exemplo, você poderia ter um arquivo de cabeçalho com o seguinte conteúdo.
// Arquivo «da_Dados.h».
#ifndef _DA_DADOS_H_
#define _DA_DADOS_H_

// Cria uma declaração de tipo baseado em estrutura, mas sem o definir.
// Como ele só vai aparecer no contexto de ponteiros, o compilador aceita ponteiros para esse “tipo incompleto”
// (o nome é esse mesmo!) como distinto dos demais tipos de ponteiros.
typedef struct _da_Dados da_Dados;

// Agora declara as funções que manipulam ponteiros para esse tipo.
da_Dados *init_da_Dados(void); // Construtor.
void free_da_Dados(da_Dados *pda); // Destrutor.

void show_n_da_Dados(const da_Dados *pda, size_t n); // Exibe um único elemento do array dinâmico.
void show_da_Dados(const da_Dados *pda); // Exibe todo o array dinâmico.
void add_read_da_Dados(da_Dados *pda); // Cria novo elemento e lê seu conteúdo.

// Outras possíveis funções.
size_t size_da_Dados(const da_Dados *pda); // Informa quantidade de elementos em uso.
void remove_last_da_Dados(da_dados *pda); // Apaga último elemento.
void remove_n_da_Dados(da_dados *pda, size_t n); // Apaga elemento em determinada posição do array dinâmico.
void add_str_da_Dados(da_dados *pda, const char *nome, const char *cpf); // Cria novo elemento com valores lidos externamente.
/* etc. */

#endif // #ifndef _DA_DADOS_H_


A efetiva definição do tipo de dados e a implementação das funções ficaria num arquivo .c separado. Esse arquivo não precisaria ser do conhecimento do usuário: em vez disso, ele poderia ser compilado e distribuído em forma de arquivo objeto (.o ou .OBJ) ou de biblioteca (.a, .so, .LIB ou .DLL), de modo que o usuário não precisaria conhecer em detalhes o que está dentro dele.
// Arquivo «da_dados.c», usado tão somente para criar uma biblioteca.
#include "da_dados.h"

// Definição do tipo de dados do qual “da_Dados” é um apelido.
struct _da_Dados {
size_t n;
struct { char nome[50], CPF[30]; } *pd;
};

// Definições das funções que foram declaradas em da_dados.h (não vou repetir aqui, pois são iguais ao que já foi escrito acima).
da_Dados *init_da_Dados(void){ /* ... */ }

void free_da_Dados(da_Dados *pda){ /* ... */ }

/* etc. etc. etc. */


No Linux, para gerar uma biblioteca estática (arquivo com sufixo “.a”), você compilaria o código acima para arquivo objeto (sufixo “.o”), e depois geraria incluiria no “archive”.
gcc -c da_dados.c  # vai gerar da_dados.o.
ar rv libda_dados.a da_dados.o # coloca o objeto dentro do archive (biblioteca).
ranlib libda_dados.a # gera um índice das funções contidas na biblioteca e o coloca dentro do archive.

sudo install da_dados.h /usr/local/include
sudo install libda_dados.a /usr/local/lib


De posse os arquivo de cabeçalho e do arquivo contendo a biblioteca (por exemplo, libda_dados.a, de acordo com o exemplo acima), você poderia construir um programa que usasse essa biblioteca. A função main() ficaria nesse programa.
// Arquivo «usa_da_dados.c».
#include <stdio.h>
#include <stdlib.h>
// etc. etc. etc.

// Nosso cabeçalho.
#include <da_dados.h>

int main(void){
da_Dados *pdad=init_da_Dados();
/* ... */
add_read_da_Dados(pdad);
/* ... */
show_da_Dados(pdad);
/* ... */
free_da_Dados(pdad);
}


O executável seria formado pela compilação do código acima e pela adição da biblioteca gerada anteriormente, com um comando parecido com o seguinte (produz o executável chamado usa_da_dados).
gcc -I/usr/local/include usa_da_dados.c -L/usr/local/lib -lda_dados -o usa_da_dados 


Está ficando difícil! Rsrs


Espero que a evolução indicada acima (faltando vários pedaços, mas era só para dar uma ideia) não o tenha deixado desanimado.

Até porque ainda há muito espaço para melhorias, tais como:

  • Em lugar de imprimir mensagens de erro no terminal e abortar o programa, uma biblioteca que se preza deveria deixar o usuário final decidir a melhor fora de tratar eventuais erros. Para tanto, possivelmente seria melhor que algumas das funções tivessem tipos de retorno diferente de void, a fim de indicar sucesso ou falha por meio de um valor booleano ou inteiro (exceto o construtor, que retorna um ponteiro, que poderia ser um ponteiro nulo em caso de erros) e ajustar o valor da variável global errno (declarada em <errno.h>) ou criando um campo adicional dentro da struct da_Dados para indicar erros no objeto, e que poderia ser consultado com uma função chamada get_status_da_Dados() ou coisa parecida.

  • Os nomes das funções estão feios, longos e inconsistentes. Um esquema melhor seria desejável.

  • (DISCUTÍVEL) Em vez de ter o campo pd como um ponteiro que requer uma segunda alocação, com o C99 ele poderia ser transformado num flexible array member (cf. https://en.wikipedia.org/wiki/Flexible_array_member), potencialmente acelerando algumas operações ao economizar um nível de indireção. A implementação fica um pouco mais complexa, mas como já estamos com uma biblioteca especializada, essa complexidade adicional poderia ser tratada dentro da biblioteca, sem transparecer para o usuário.

Referente a usar um outro ponteiro para fazer a realocação, confesso que ainda estou tentando fazer rsrs


Mas isso é só copiar. A forma é exatamente aquela que eu mostrei (e repeti hoje em vários pedaços desta postagem).


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)

3. Re: Struct, Ponteiros, Malloc/Realloc

Paulo
paulo1205

(usa Ubuntu)

Enviado em 11/01/2021 - 18:01h

princknoby escreveu:

Olá,

Estou enfrentando um grande "problema", estou procurando onde está o erro a dias e não consigo encontrar...

Tenho o seguinte código (estou apenas brincando, tentando entender alguns conceitos, não é nada para fins profissionais).

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

typedef struct dados {
char nome[50], CPF[30];
} Dados;

void _fflush();
void add(Dados *pd, int n);
void show(Dados *pd, int n);

int main(int argc, char *argv[]) {
int n = 0, op;
Dados *pd = malloc(n * sizeof(pd));


Aqui tem um erro e um comportamento que pode variar entre diferentes implementações.

O comportamento indeterminado é consequência de fazer n igual a zero: a implementação pode lhe devolver um ponteiro para uma área com tamanho zero ou pode lhe devolver um ponteiro nulo. Em qualquer caso, você não deve considerar que o espaço que porventura tenha sido alocado (caso o ponteiro não seja nulo) está apto para receber um valor. Isso pode ser um problema abaixo.

Desconsiderando o valor de n como zero (que acaba jogando o tamanho para zero de qualquer forma), o aspecto geral da construção que você usou está errado. Você provavelmente quis dizer o seguinte.
Dados *pd=malloc(n * sizeof *pd); 


A explicação é que você quer alocar memória para n elementos cujo tipo é aquele que é apontado por pd. E a forma de referir-se àquilo que é apontado por pd é “*pd”. Do modo como você fez, sem o asterisco, você acabou instruindo o compilador a alocar espaço para n ponteiros, cujo tamanho certamente é menor do que o tamanho da estrutura Dados.

  system("clear"); 


Evite usar system(). Se você quiser apenas limpar a tela, veja a 12ª mensagem do seguinte tópico https://www.vivaolinux.com.br/topico/C-C++/Preciso-fazer-um-programa-em-C-para-cadastra-alunos-consu....


do {
printf("Digite uma opção: ");
scanf("%d", &op);
_fflush();
if (op == 1) {
add(&pd[n], n);


Se a chamada acima ocorrer na primeira iteração, n vale 0, e pd ou será nulo ou apontará para uma área de dados cujo espaço não comporta elementos (e mesmo que comportasse, ainda seria espaço para apenas um ponteiro, não para uma estrutura do tipo Dados, a não ser que você já tenha corrigido o erro de formato que eu apontei acima).

Possivelmente seria melhor você inverter a ordem entre a realocação e a adição ao final do vetor. Desde que corrija também a realocação, que sofre não apenas do mesmo erro da alocação com malloc(), mostrado acima, mas também de um segundo erro.

      n++;
pd = realloc(pd, n * sizeof(pd));


A especificação do operando de sizeof aqui tem o mesmo erro apontado acima, na chamada a malloc(): você deveria ter dito “sizeof *pd” em vez de “sizeof pd” (os parênteses não são necessários quando o operando é uma expressão; só seriam necessários se você tivesse um nome de tipo — por exemplo: “sizeof(Dados)”).

O outro erro, infelizmente muito comum, é usar a mesma variável ponteiro que indica a área a ser realocada como destinatária do valor de retorno de realloc(). Isso é errado porque realloc() pode não conseguir efetivamente realocar a memória com a capacidade solicitada; nesse caso, a área original seria preservada totalmente intacta, e o valor retornado pela função seria um ponteiro nulo. Quando você usa a mesma variável como destino do valor de retorno, corre o risco de perder a referência aos dados que existiam antes caso a realocação falhe, de modo que nem mesmo vai conseguir liberar a memória para eles depois.

A forma correta de fazer é usar uma segunda variável para receber o ponteiro para a área realocada, e substituir o valor da variável original apenas após ter certeza de que a realocação foi bem sucedida.
Dados *ptr;
size_t size;

/* ... */

size_t new_size=size+1;
void *new_ptr=realloc(new_size * sizeof *ptr);
if(new_ptr){
size=new_size;
ptr=new_ptr;
}
else{
perror("Realocação de memória falhou");
exit(1);
/*
Em vez de chamar exit(), você poderia dar outro tratamento menos radicial. Em alguns contextos,
pode ser interessante prosseguir com os valores anteriores de ptr e size, que são
preservados, mesmo que realloc() venha a falhar.
*/
}


    }
else if (op == 2) { show(pd, n); }
} while (op != 0);

free(pd);
return 0;
}

void _fflush() {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}

void add (Dados *pd, int n) {
printf("Digite o nome: ");
scanf("%49[^\n]", pd->nome);
_fflush();

printf("Digite o CPF: ");
scanf("%29[^\n]", pd->CPF);
_fflush();
}

void show (Dados *pd, int n) {
for (unsigned int i = 0; i < n; i++) {
printf("%s\t%s\n", pd[i].nome, pd[i].CPF);
}
}


Meu objetivo é ser capaz de ler Nome e CPF de forma dinâmica (até que o usuária decida que não quer adicionar mais nenhuma pessoa), e ter a opção do programa de adicionar alguem (1), mostrar os nomes e cpf adicionados (2) e sair do programa (0)

Porém sempre o Primeiro Nome e CPF lido quando vou listá-los (opção 2), os 2 primeiros dígitos do CPF, SEMPRE são "engolidos" e nunca aparecem, vou tentar colocar um print aqui da saída.

E para minha surpresa, quando compilo e rodo o programa no seguinte site: https://www.onlinegdb.com/online_c_compiler o programa funciona como o esperado!

Enfim, não sei onde está o problema, não sei se essa informação é útil, mas minha versão do gcc é a seguinte: gcc version 10.2.0 (GCC)

Desde já, obrigado




... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


4. Re: Struct, Ponteiros, Malloc/Realloc

João Paulo
princknoby

(usa Arch Linux)

Enviado em 13/01/2021 - 08:25h

paulo1205 escreveu:

princknoby escreveu:

Olá,

Estou enfrentando um grande "problema", estou procurando onde está o erro a dias e não consigo encontrar...

Tenho o seguinte código (estou apenas brincando, tentando entender alguns conceitos, não é nada para fins profissionais).

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

typedef struct dados {
char nome[50], CPF[30];
} Dados;

void _fflush();
void add(Dados *pd, int n);
void show(Dados *pd, int n);

int main(int argc, char *argv[]) {
int n = 0, op;
Dados *pd = malloc(n * sizeof(pd));


Aqui tem um erro e um comportamento que pode variar entre diferentes implementações.

O comportamento indeterminado é consequência de fazer n igual a zero: a implementação pode lhe devolver um ponteiro para uma área com tamanho zero ou pode lhe devolver um ponteiro nulo. Em qualquer caso, você não deve considerar que o espaço que porventura tenha sido alocado (caso o ponteiro não seja nulo) está apto para receber um valor. Isso pode ser um problema abaixo.

Desconsiderando o valor de n como zero (que acaba jogando o tamanho para zero de qualquer forma), o aspecto geral da construção que você usou está errado. Você provavelmente quis dizer o seguinte.
Dados *pd=malloc(n * sizeof *pd); 


A explicação é que você quer alocar memória para n elementos cujo tipo é aquele que é apontado por pd. E a forma de referir-se àquilo que é apontado por pd é “*pd”. Do modo como você fez, sem o asterisco, você acabou instruindo o compilador a alocar espaço para n ponteiros, cujo tamanho certamente é menor do que o tamanho da estrutura Dados.

  system("clear"); 


Evite usar system(). Se você quiser apenas limpar a tela, veja a 12ª mensagem do seguinte tópico https://www.vivaolinux.com.br/topico/C-C++/Preciso-fazer-um-programa-em-C-para-cadastra-alunos-consu....


do {
printf("Digite uma opção: ");
scanf("%d", &op);
_fflush();
if (op == 1) {
add(&pd[n], n);


Se a chamada acima ocorrer na primeira iteração, n vale 0, e pd ou será nulo ou apontará para uma área de dados cujo espaço não comporta elementos (e mesmo que comportasse, ainda seria espaço para apenas um ponteiro, não para uma estrutura do tipo Dados, a não ser que você já tenha corrigido o erro de formato que eu apontei acima).

Possivelmente seria melhor você inverter a ordem entre a realocação e a adição ao final do vetor. Desde que corrija também a realocação, que sofre não apenas do mesmo erro da alocação com malloc(), mostrado acima, mas também de um segundo erro.

      n++;
pd = realloc(pd, n * sizeof(pd));


A especificação do operando de sizeof aqui tem o mesmo erro apontado acima, na chamada a malloc(): você deveria ter dito “sizeof *pd” em vez de “sizeof pd” (os parênteses não são necessários quando o operando é uma expressão; só seriam necessários se você tivesse um nome de tipo — por exemplo: “sizeof(Dados)”).

O outro erro, infelizmente muito comum, é usar a mesma variável ponteiro que indica a área a ser realocada como destinatária do valor de retorno de realloc(). Isso é errado porque realloc() pode não conseguir efetivamente realocar a memória com a capacidade solicitada; nesse caso, a área original seria preservada totalmente intacta, e o valor retornado pela função seria um ponteiro nulo. Quando você usa a mesma variável como destino do valor de retorno, corre o risco de perder a referência aos dados que existiam antes caso a realocação falhe, de modo que nem mesmo vai conseguir liberar a memória para eles depois.

A forma correta de fazer é usar uma segunda variável para receber o ponteiro para a área realocada, e substituir o valor da variável original apenas após ter certeza de que a realocação foi bem sucedida.
Dados *ptr;
size_t size;

/* ... */

size_t new_size=size+1;
void *new_ptr=realloc(new_size * sizeof *ptr);
if(new_ptr){
size=new_size;
ptr=new_ptr;
}
else{
perror("Realocação de memória falhou");
exit(1);
/*
Em vez de chamar exit(), você poderia dar outro tratamento menos radicial. Em alguns contextos,
pode ser interessante prosseguir com os valores anteriores de ptr e size, que são
preservados, mesmo que realloc() venha a falhar.
*/
}


    }
else if (op == 2) { show(pd, n); }
} while (op != 0);

free(pd);
return 0;
}

void _fflush() {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}

void add (Dados *pd, int n) {
printf("Digite o nome: ");
scanf("%49[^\n]", pd->nome);
_fflush();

printf("Digite o CPF: ");
scanf("%29[^\n]", pd->CPF);
_fflush();
}

void show (Dados *pd, int n) {
for (unsigned int i = 0; i < n; i++) {
printf("%s\t%s\n", pd[i].nome, pd[i].CPF);
}
}


Meu objetivo é ser capaz de ler Nome e CPF de forma dinâmica (até que o usuária decida que não quer adicionar mais nenhuma pessoa), e ter a opção do programa de adicionar alguem (1), mostrar os nomes e cpf adicionados (2) e sair do programa (0)

Porém sempre o Primeiro Nome e CPF lido quando vou listá-los (opção 2), os 2 primeiros dígitos do CPF, SEMPRE são "engolidos" e nunca aparecem, vou tentar colocar um print aqui da saída.

E para minha surpresa, quando compilo e rodo o programa no seguinte site: https://www.onlinegdb.com/online_c_compiler o programa funciona como o esperado!

Enfim, não sei onde está o problema, não sei se essa informação é útil, mas minha versão do gcc é a seguinte: gcc version 10.2.0 (GCC)

Desde já, obrigado




... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


Obrigado pela atenção Paulo!

Acho que consegui entender a questão de declarar n = 0

Fiz o programa declarando n =1 :
int n = 1;
Dados *pd = malloc(n * sizeof *pd) // Não entendi essa questão muito bem... *pd e Dados tem o mesmo tamanho?!
// Eu deveria usar sizeof *pd ou sizeof(Dados)?


E na chamada da função para adicionar os dados, eu chamo a função assim:
add(&pd[n-1]); 

Tem alguma forma mais bonitinha ao invés de usar [n-1]?
Eu pensei em passar todos os elementos da struct e usar uma variável de controle dentro da própria função, o problema foi que não consegui passar todos os elementos, fiquei confuso entre:
add(&pd); e add(pd); 


Usando
add(pd); 
e recebendo na função:
void add (Dados *pd);  

Acabou que não funcionou e eu consegui adicionar dados apenas a primeira posição da struct :/

Está ficando difícil! Rsrs

Referente a usar um outro ponteiro para fazer a realocação, confesso que ainda estou tentando fazer rsrs

Novamente, muito obrigado Paulo



5. Re: Struct, Ponteiros, Malloc/Realloc [RESOLVIDO]

leandro peçanha scardua
leandropscardua

(usa Ubuntu)

Enviado em 13/01/2021 - 08:46h

Roda no gdb
https://www.ic.unicamp.br/~rafael/materiais/gdb.html


6. Re: Struct, Ponteiros, Malloc/Realloc [RESOLVIDO]

João Paulo
princknoby

(usa Arch Linux)

Enviado em 27/01/2021 - 12:42h


Obrigado por todo o conteúdo e dicas passadas Paulo, estarei estudando e implementando todas as suas dicas!
Muito obrigado!

Estarei fechando o tópico, pois você esclareceu minhas dúvidas e ainda me acrescentou muitos outros conhecimentos!






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts