Ir ao conteúdo

Posts recomendados

Postado

Boa noite!

Bom preciso de uma dica/ajuda em uma problema...

tenho duas structs, LIST e NO.  lista duplamente encadeada.

//insiro/crio 100.000 nos.

 

struct no{
    char *nome;
    char sexo;
    float nota;
    float media;
    int id;    
    struct no *next, *prev;
};

struct list{
    long int size_list;
    struct no *begin, *end;
};

typedef struct no NO;
typedef struct list LIST;

for(int y = 0; y < 100000; y++)
            inserir_inicio(*list); 

void CreateNo(NO **no)
{
    *no = (NO*)calloc(1,sizeof(NO));

    if (*no) {
        (*no)->nome = "cicero";
        (*no)->sexo = 'M';
        (*no)->nota = 0.0;
        (*no)->media = 1;
        (*no)->id++;
        (*no)->next = NULL;
        (*no)->prev = NULL;

        is_Ok = true;
    }
    else {
        printf("ERRO: Ao alocar memoria em 'void CreateNo(NO **no)'\n");
        is_Ok = false;
        return;
    }
}

void inserir_inicio(LIST *list)
{
    NO *no;
    CreateNo(&no);

    if (is_Ok) {    
        no->next = list->begin;
        list->begin = no;
        list->size_list++;
    }
}

// destroy os nos
void DestoyList(LIST **list)
{
	if ((*list)->begin == NULL)
	{
		free(*list);
		printf("Lista vazia!\n\n");
	}
	else {
		NO *p = (*list)->begin, *tmp = NULL;
		
		while (p != NULL) {			
			tmp = p;
			p = p->next;

			/*tmp->nome = "0";
			tmp->sexo = '\0';
			tmp->id = 0;
			tmp->media = 0;*/

			free(tmp);
		}
		free(p);
		(*list = NULL);		
	}
}

o problema é o seguinte:

quando inicio o aplicativo ele começão com 512kb, quando criando  100.000  na memoria heap, aumenta para mais ou menos uns 7,8mb, bom ate aqui tudo oks

mas quando delete este nos com o free() esperava eu q retornasse para os mesmo 512kb inicias do aplicativo, mas isso não acontece, ele retorna para 1,3mb.

minha pergunta é: porque não deleta tudo, já setei zeros e para todos os membros da struct no mas mesmo assim não deleta tudo.

Se alguém poder meda da uma dica. OBS: não quero código  pronto eu quero apenas dicas se possível bem explicada porque sou meio novato em c kkk.

Postado

@cgm2k7 Para se certificar que não há vazamento de memória use uma ferramenta, como o address sanitizer ou o valgrind. é o comum.

 

Sobre seu programa

 

Poste algo compilável. É mais fácil para alguém te ajudar

 

Evite misturar os dados com o container, no caso os dados com a lista.

 

A lista é uma coleção de nós. Um nó não é uma lista e nem um dado. Em geral um nó tem um ponteiro para um dado. Porque? Porque se espera que as mesmas funções sirvam para QUALQUER lista. Uma lista é só uma lista.

 

Nesse sentido

 

struct no
{
    char*      nome;
    char       sexo;
    float      nota;
    float      media;
    int        id;
    struct no *next, *prev;
};

 

Não é nada bom.

 

Compare
 

typedef struct
{
    char* nome;
    char  sexo;
    float nota;
    float media;
    int   id;

}   Dado;

 

Esse é o seu dado. Nada tem a ver com a lista e se colocar lá dentro só vai trabalhar mais por menos resultado. 

 

typedef struct st_no
{
    struct st_no* prev;
    struct st_no* next;
    Dado*         dado;

} Node;

 

Esse é o nó da lista. Não deve ter nada específico ao Dado em si ou vai ter que mudar a cada programa e ninguém quer isso. Em geral isso é void* justamente para ser genérico.

 

A lista

 

struct list
{
    long int   size_list;
    struct no *begin, *end;
};

 

Prefira apenas declarar o nome, se possível em um arquivo separado. Assim ao criar outras listas usa o mesmo #include e não precisa alterar nada.

 

Entenda que quando declara algo em C está declarando um nome e um tipo. Nesse caso por exemplo está declarando begin e end. Talvez não devesse usar esses nomes que são comuns e reservados em outras linguagens, como C++. Considere os clássicos head e tail, inicio e fim, algo assim. 

Mas qual o tipo de begin? É struct no*. Qual o tipo de end?  end é struct no*. Como está aprendendo talvez deva evitar esse vício de colocar o asterisco no lugar errado da declaração. Se end é struct no* é claro que *end é struct no. É C. Mas está em uma declaração. O asterisco tem personalidade múltipla em C.

 

Ao definir nomes use alguma convenção, como o clássico de reservar a primeira letra em maiúscula para isso. 

 

Exemplo
 

typedef struct
{
    size_t size;
    Node*  head;
    Node*  tail;

} Lista;

 

Note que C tem esse tipo normalmente reservado para essas coisas: size_t é um inteiro longo sem sinal. Prefira isso.

Create_No() e inserir_inicio()

 

void CreateNo(NO** no)
{
    *no = (NO*)calloc(1, sizeof(NO));

    if (*no)
    {
        (*no)->nome  = "cicero";
        (*no)->sexo  = 'M';
        (*no)->nota  = 0.0;
        (*no)->media = 1;
        (*no)->id++;
        (*no)->next = NULL;
        (*no)->prev = NULL;

        is_Ok = true;
    }
    else
    {
        printf("ERRO: Ao alocar memoria em 'void CreateNo(NO **no)'\n");
        is_Ok = false;
        return;
    }
}

 

Acho que aí está errado mesmo.

 

Entenda:

 

  • Se usa Create_No() devia usar Insert()  ou não? E se está implementando uma única disciplina de inserção use apenas insert(). Se usou a primeira letra maiúscula em create porque não em insert()?
  • Se só chama Create() dentro de Insert() e está inserindo dezenas de milhares de nós considere escrever Create() dentro de Insert()

Ao invés de usar Create(NO** no) prefira sempre retornar o ponteiro para o nó, escrevendo
 

    NO* Create(Dado*);

 

Tudo fica mais simples e legível. Passa o endereço de um dado, recebe o endereço de um NO com o ponteiro dentro

faça a mesma coisa para criar e destruir a lista: é o simples.

 

Ainda sobre Create

 

void CreateNo(NO** no)
{
    *no = (NO*)calloc(1, sizeof(NO));

    if (*no)
    {
        (*no)->nome  = "cicero";
        (*no)->sexo  = 'M';
        (*no)->nota  = 0.0;
        (*no)->media = 1;
        (*no)->id++;
        (*no)->next = NULL;
        (*no)->prev = NULL;

        is_Ok = true;
    }
    else
    {
        printf("ERRO: Ao alocar memoria em 'void CreateNo(NO **no)'\n");
        is_Ok = false;
        return;
    }
}

 

NÃO use nada global. Nunca. Para nada. É um desastre, Se acha que precisa de is_Ok considere que podia ter escrito
 

    *no = (NO*)calloc(1, sizeof(NO));
    is_Ok = no == NULL;

 

Isso aqui está errado

 

        (*no)->nome  = "cicero";

 

Isso é uma bomba-relógio. nome é char* e está dentro de todos os nós da lista. "cicero" é const char* e só existe dentro de Create(0 e enquanto estiver rodando. Claro, nesse caso vai continuar vivo porque é estático. Mas está errado. 

Ao apagar um nó você vai apagar o nome. E se chamar free() com esse valor vai cancelar seu programa porque como escrever o nome aponta para uma constante.

 

Você podia escrever o comum:
 

void destruir(Lista*);
void criar(Lista*);
int  inserir(Dado*, Lista*);

 

Talvez ache mais legível: criar a lista devolve o endereço, apagar a lista devolve um NULL para invalidar o endereço, inserir insere um dado numa lista.

 

Sobre isso

 

    if (*no)
    {
        (*no)->nome  = "cicero";
        (*no)->sexo  = 'M';
        (*no)->nota  = 0.0;
        (*no)->media = 1;
        (*no)->id++;
        (*no)->next = NULL;
        (*no)->prev = NULL;

 

 

Acho que o que tentou fazer é cria algo como uma factory function, que devolve um nó para poder ir testando a lista. Sua ideia é muito boa. A implementação nem tanto, porque precisa alocar um nome a cada chamada.

 

Veja um exemplo clássico de factory function para esse caso 
 

Dado* fabrica()
{
    Dado*      novo = (Dado*)malloc(sizeof(Dado));
    static int id   = 1;
    novo->nome = (char*)malloc(14);
    sprintf(novo->nome, "Cicero %06d", id);
    novo->sexo  = 'M';
    novo->nota  = (rand() % 1000) / 100.f;
    novo->media = (float)id;
    novo->id    = id;
    id += 1;
    return novo;
}

 

A cada chamada CRIA de fato um dado e devolve o endereço. Todos os valores são preenchidos.

 

Veja essa conveniente função
 

int     mostra_dado(Dado* A)
{
    if (A == NULL) return -1;
    printf("\"%20s\" Id: %6d %c Nota: %.2f Media: %.2f\n",
        A->nome, A->id, A->sexo, A->nota, A->media);
    return 0;
}

 

E esse programa

 

int main(void)
{
    for (int i = 0; i < 8; i += 1)
    { 
        Dado* temp = fabrica();
        mostra_dado(temp);
        free(temp);
    }
    return 0;
}

 

Mostra

 

"       Cicero 000001" Id:      1 M Nota: 0.41 Media: 1.00
"       Cicero 000002" Id:      2 M Nota: 4.67 Media: 2.00
"       Cicero 000003" Id:      3 M Nota: 3.34 Media: 3.00
"       Cicero 000004" Id:      4 M Nota: 5.00 Media: 4.00
"       Cicero 000005" Id:      5 M Nota: 1.69 Media: 5.00
"       Cicero 000006" Id:      6 M Nota: 7.24 Media: 6.00
"       Cicero 000007" Id:      7 M Nota: 4.78 Media: 7.00
"       Cicero 000008" Id:      8 M Nota: 3.58 Media: 8.00

 

Acho que entendeu a diferença: pode passar isso direto para insere()...

 

 

 

 

Postado

valeu muito obrigado... por essa dica pois abriu mais minha mente, eu até já lir sobre isso não levei muito a serio, mas agora ficou mais claros soubre essa dicas de em c...

 

MAs uma duvida: vir em alguns livros e apostilhas que retorna valor não é uma boa pratica de programação, porque usa mais processo e memoria porque ele faz uma segunda copia dos dados a serem retornado.

Ou  neste caso retornando um endereço para uma dado "ponteiro" seria não run!

 

Exemplo:

 

int a = 100, b = 1000;

 

int retorn_valor(int a, int b)

{

    return a * b;

}  

neste caso ele faz uma copia na memoria a mais correto!?.

 

e se for assim:

 

int* retorn_valor(int *a, int *b)

{

   return a * b;

}

neste caso é mais otimizado certo... porque não sera feita uma copia dos dados na memoria...

 

agradeço mesmo pelas dicas minto obrigado...

Postado

Retornar um ponteiro para Dado em fabrica() por exemplo implica em retornar 4 ou 8 bytes conforme esteja gerando código em 32 ou 64 bits.

Mas a estrutura tem 24 ou 20 bytes, então claro que copiar faz diferença. E nem sempre a diferença é assim pequena. 

Claro que alocar na hora e passar o ponteiro deixa uma responsabilidade e é um risco.

 

Por outro lado passar o endereço de uma área permite que o mesmo espaço seja reutilizado...

 

No geral é melhor considerar...  o particular :D  . Cada programa tem uma história. Mas em geral alocar memória e manipular endereços é a única opção porque a memória tem fim e os dados muitas vezes não.  Mesmo que isso seja feito de forma transparente pela linguagem.

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...