Ir ao conteúdo
  • Cadastre-se

Programa em C: malloc() dentro de outra função


Pedrohtico

Posts recomendados

Olá, eu estou fazendo um programa que lê imagens PPM, mas estou tendo um problema. A seguinte função lê a imagem e guarda dentro de variáveis criadas na main. O problema está no ponteiro de ponteiro, ou seja, a matriz "pixel":

typedef struct{
    int r, g, b;
}matriz_pixel;

void ler_imagem(matriz_pixel **pixel, int *largura, int *altura, int *max, char *code, char *argv[]){  // Lê os primeiros valores do arquivo PPM

    int i, j;
    FILE *imagem;
    char nome[101];

    scanf("%s", nome);

    imagem = fopen(nome, "r");   // Abre o arquivo da imagem PPm
    if(imagem == NULL){
        printf("Erro na abertura do arquivo!\n");
        exit(1);
    }

    fscanf(imagem, "%s", code);
    fscanf(imagem, "%d", largura);
    fscanf(imagem, "%d", altura);
    fscanf(imagem, "%d", max);

    pixel = (matriz_pixel**) malloc (*altura * sizeof(matriz_pixel*));  // Aloca a matriz de struct que guardará os valores RGB
    if(pixel == NULL){
        printf("Erro de memoria!\n");
        exit(1);
    }
    for(i = 0; i < *altura; i++){
        pixel[i] = (matriz_pixel*) malloc (*largura * sizeof(matriz_pixel));
        if(pixel[i] == NULL){
            printf("Erro de memoria!\n");
            exit(1);
        }
    }

    printf("PIXEL 1: R: %d G: %d B: %d\n", pixel[0][0].r, pixel[0][0].g, pixel[0][0].b);

    for(i = 0; i < *altura; i++){
        for(j = 0; j < *largura; j++){
            fscanf(imagem, "%d", &pixel[i][j].r);
            fscanf(imagem, "%d", &pixel[i][j].g);
            fscanf(imagem, "%d", &pixel[i][j].b);
        }
    }

    printf("PIXEL 1: R: %d G: %d B: %d\n", pixel[0][0].r, pixel[0][0].g, pixel[0][0].b);

    fclose(imagem);
}

Essa segunda função eu fiz apenas para saber se os valores estavam sendo guardados corretamente:

 

void escrever_variaveis(matriz_pixel **pixel, int largura, int altura, int max, char *code){

    printf("Code: %s\n", code);
    printf("Largura: %d\n", largura);
    printf("Altura: %d\n", altura);
    printf("Tonalidade max: %d\n", max);
    printf("PIXEL 1: R: %d G: %d B: %d\n", pixel[0][0].r, pixel[0][0].g, pixel[0][0].b);

}

E então, tem a função main(), que apenas inicializa as variáveis e chama as duas funções anteriores:

 

int main(int argc, char *argv[]){

    int i, j;

    matriz_pixel **pixel;            // Variáveis dos atributos da imagem PPM
    int max, largura, altura;
    char code[3];

    ler_imagem(pixel, &largura, &altura, &max, code, argv);

    escrever_variaveis(pixel, largura, altura, max, code);

    free(pixel);
    for(i = 0; i < altura; i++){
        free(pixel[i]);
    }

    return 0;
}

Eu coloquei um printf para mostrar o valor RGB guardado dentro do primeiro pixel da matrix de struct "pixel". Esse printf foi colocado 3 vezes. A primeira vez ela printa o lixo logo depois de alocar a matriz pixel. Na segunda o valor após ler a imagem PPM. E a terceira, que fica na segunda função, deveria mostrar os mesmos valores que a segunda vez. Entretanto, o cmd dá mal funcionamento nesse printf e encerra o programa. Eu imagino que seja algum problema com a alocação dentro da função, como por exemplo se ela desalocasse quando acabasse a função, pois os valores são printados na segunda vez de forma correta. Apenas a terceira vez dá problema. Agradeço a ajuda desde já.

 

Edit 1: Ao retirar o terceiro printf, a variável (matriz_pixel **pixel) no nome da função e (pixel) na chamada dela, ela contina dando esse erro. Então eu descobri que tanto esse terceiro printf quanto o free(pixel) no final do main estão dando esse erro (se eu retirar essas duas partes, o programa funciona normalmente), o que me faz acreditar que é justamente algum problema com a alocação da matriz "pixel" dentro da função "ler_imagem".

 

Edit 2: Se eu inicializo a variável "matriz_pixel pixel**" globalmente, o programa para de dar erro e funciona normalmente.

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

Todo os erros do seu programa se devem a causa de não entender corretamente o uso dos ponteiros.
você deve pensar que um int* é a direção de um int, e que um int** é a direção de um int*, pois no caso dos ponteiros duplos triplos, etc, trata-se de ponteiro a ponteiros. Siga o exemplo:

int x = 10;

int* p = &x;

int** q = &p;

Seguindo essa lógica você deveria passar um ponteiro duplo para a função porém sua função deveria ter um ponteiro triplo. Por exemplo em main quando você invoca a função:

ler_imagem(pixel, &largura, &altura, &max, code, argv);

 

você está passando pixel que é um duplo ponteiro porém a função está declarada como:
void ler_imagem(matriz_pixel **pixel, int *largura, int *altura, int *max, char *code, char *argv[])


Se estiver fazendo isso dentro do main equivaleria a fazer:
matriz_pixel **pixel1;
matriz_pixel **pixel2;

pixel1 = pixel2;

Da para ver algo errado aí?
Claro! você está fazendo pixel1 apontar ao valor de pixel2 não a pixel2. O problema é que pixel2 aponta a LIXO, pois pixel2 NÃO APONTA NADA.

Vamos fazer uma prova:

Primeiro faremos passando o resultado para a função:

#include<stdio.h>

void reserva( int **m2 ){
    printf("Na funcao: %d\n", m2  );
 
}

int main(){
    int**m1;
    printf("Em main: %d\n", m1);
    
    reserva(m1);
    
    return 0;
}

O importante não é o que imprime, e sim o valor que se repete.
Agora vamos ver a mesma coisa em main:

#include<stdio.h>

int main(){
    int **m1;
    int **m2;
    printf("m1: %d\n", m1);
    printf("m2: %d\n", m2);

    return 0;
}

Se repete verdade? Claro que sim! Ambas formas são equivalentes. Qual problema vemos nisso? Pois existe muita diferença entre a direção de um ponteiro e o valor que guarda um ponteiro. A direção de um ponteiro é a direção de si mesmo, e o valor que guarda um ponteiro é a direção de outro objeto apontado já seja esse lixo na memoria, null, etc.

Quando eu tenho:
int **m;

Se eu quero saber a direção do objeto m[0][0] eu faço &m[0][0] pondo o & adiante, porém existe uma notação mais fácil para isso, e é fazendo simplesmente m. COMO? A forma &m[0][0] aponta ao objeto da linha 0 e coluna 0, porém é exatamente a mesma coisa fazer m, sem nada. Veja o exemplo:

 

#include<stdio.h>

int main(){
    int m[3][3];
    printf("Forma normal: %d\n", &m[0][0]);
    
    printf("Forma semelhante porém simplificada: %d\n", m);
    
    printf("Ambas imprimem a mesma coisa\n");    

    return 0;
}


Então você ta passando a direção zero zero da matriz para a função, e ta guardando a direção que malloc retorna onde começa o array em vez de guardar no ponteiro. E pior ainda é que como falei esse ponteiro ainda nem é uma matriz, ele aponta um lugar aleatório da memória, pois quando você declara o ponteiro em main você nem dá um valor de nullo para ele, ou seja ela aponta a qualquer porcaria na memória e nem é um array2d ainda.
x-x-everywhere-bugs-bugs-everywhere.jpg


O que você quer é modificar o ponteiro e não o que aponta o ponteiro por isso para manipular um ponteiro duplo você precisa de um ponteiro triplo, e não há nada mais que falar.

 

Neste tópico você pode encontrar o que anda buscando que é a forma correta de reservar memória para uma matriz ao passar ponteiro:
https://stackoverflow.com/questions/15062718/allocate-memory-2d-array-in-function-c/15062765#15062765

 

Basicamente existe 2 formas, a primeira aloca a memória de forma aleatória, é dizer, malloc vai por o seu array espalhado pela memória, ou seja de forma fragmentada. O problema da memória fragmentada é que a memória fica pegando saltos para poder ler o array, isso traduz em perda de tempo, um programa mais lento. Ao se tratar de um programa que manipula imagem lhe interessa a segunda forma que é reservar memória em um só bloco ou seja a matriz será continua.

Deixo claro que a forma de liberar memória também é importante e está também nessa pagina.
Vamos deixar aqui a forma de se fazer a coisa corretamente, cabe a você adapta-lo ao seu projeto.

A primeira forma é essa que vou por a continuação, é a forma na qual malloc reserva memória de forma Não contigua. A vantagem disso é que o sistema vai encontrar memória mais facilmente pois vai usar a memória mais eficientemente desde o ponto de vista de que aproveita pequenos pedaços que estão espalhados pela ram, encontrar um pedaço grande pode não ser possível, ou pelo menos mais difícil, porém isso não é do todo muito certo pois hoje em dia os computadores tem de 2 a mais gigas, mais que suficiente para alojar qualquer imagem.

Reserva de memória forma não contigua:

void allocate_mem(int*** arr, int n, int m){
  *arr = (int**)malloc(n*sizeof(int*));
  for(int i=0; i<n; i++)
    (*arr)[i] = (int*)malloc(m*sizeof(int));
} 

Liberar memória de forma não contigua:

void deallocate_mem(int*** arr, int n){
    for (int i = 0; i < n; i++)
        free((*arr)[i]);
    free(*arr); 
}


Agora veremos como reservar de forma contigua a memória:

int* allocate_mem(int*** arr, int n, int m){
  *arr = (int**)malloc(n * sizeof(int*));
  int *arr_data = malloc( n * m * sizeof(int));
  for(int i=0; i<n; i++)
     (*arr)[i] = arr_data + i * m ;
  return arr_data; //free point
} 


E por último como liberar a memória de forma contigua:

void deallocate_mem(int*** arr, int* arr_data){
    free(arr_data);
    free(*arr);
}


Como falei se você quer manipular um ponteiro duplo você precisa de um ponteiro triplo, se manipular um ponteiro triplo você precisa de um quádruplo, e assim por diante.

Agora falando desde o ponto de vista sobre eficiência no seu programa, saiba que um vetor é muito mais rápido que uma matriz pois no caso da matriz a memória pegaria muitos saltos no caso das matrizes, coisa que como já falei pode ser melhorado através de certas técnicas. E não falo qualquer técnicas não to falando em técnicas usadas por programas profissionais tipo:
int linhas = 10;
int colunas = 10;
int array[ linhas*colunas ];

Nesse caso você teria um vetor porém funcionaria como matriz mas com a eficiência de um vetor. O mesmo poderia ser feito com malloc:
int *array = malloc( linhas*colunas*sizeof(int) );

 

Talvez você se pergunte como faria para manipular isso com um for:

int i, z;
for ( i = 0, i < 10; i++ ) {
    for ( z = 0; z < 10; z++ ) {
        printf ( "%d", array[ i * z ] );
    }
}

Como falei essa forma tem a funcionalidade de uma matriz porém trata-se de um vetor, muito mais rápido na hora de percorrer cada casinha, mais fácil de reservar memória e liberar, somente são vantagens. :lol:

Saiba também que as matrizes podem ser tratadas como vetor:
 

#include<stdio.h>

int main(){
    
    int m[3][3]={
        { 1,2,3 },
        { 4,5,6 },
        { 7,8,9}
    };
    int i;
    for ( i=0; i<3*3 ; i++ ) {
        printf("%d, ", m[0][i]);
    }

    return 0;
}

Como você pode ver com um só for passo por todas as casinhas da matriz :eek:. Não sei se funciona com matrizes dinâmicas, porém certamente se funciona será com a forma de reservar dinamicamente de forma contigua como expliquei antes, pois da forma não contigua a matriz estará espalhada pela memória e não funcionaria, pois esse tipo de laço só funciona por que a matriz é alojada na memória como um array ja que a memória na pilha é alojada de forma contigua, caso contrario não funcionaria. Se quiser saber mais sobre o tema pergunte. :thumbsup:

Bom.. espero ter ajudado, faça suas provas, tome seu tempo, consulte o google, pergunte a seu professor... :atirador:

Sorte!

  • Curtir 2
Link para o comentário
Compartilhar em outros sites

Então vangodp, ajudou bastante e acho que entendi, mas não está dando certo na prática:

 

Eu mudei para um vetor, como você mostrou ser mais prático (e realmente vai ser, em outras funções que vou utilizar no programa) e, pela lógica, quando eu passasse para a função, a função deveria receber um **pixel, já que foi criado um *pixel. Ficou dessa forma:

 

void ler_imagem(matriz_pixel **pixel, int *largura, int *altura, int *max, char *code, char *argv[]){  // Lê os primeiros valores do arquivo PPM

    int i, j;
    FILE *imagem;
    char nome[101];

    scanf("%s", nome);

    imagem = fopen(nome, "r");   // Abre o arquivo da imagem PPM
    if(imagem == NULL){
        printf("Erro na abertura do arquivo!\n");
        exit(1);
    }

    fscanf(imagem, "%s", code);
    fscanf(imagem, "%d", largura);
    fscanf(imagem, "%d", altura);
    fscanf(imagem, "%d", max);

    *pixel = (matriz_pixel*) malloc((*largura)*(*altura)*sizeof(matriz_pixel));
    if(*pixel == NULL){
        printf("Erro de memoria!\n");
        exit(1);
    }

    printf("PIXEL 1: R: %d G: %d B: %d\n", pixel[0]->r, pixel[0]->g, pixel[0]->b);

    for(i = 0; i < *altura; i++){
        for(j = 0; j < *largura; j++){
            fscanf(imagem, "%d", &pixel[(*altura)*i + j]->r);
            fscanf(imagem, "%d", &pixel[(*altura)*i + j]->g);
            fscanf(imagem, "%d", &pixel[(*altura)*i + j]->b);
            printf("PIXEL 1: R: %d G: %d B: %d  (%d) (%d)\n", pixel[(*altura)*i + j]->r, pixel[(*altura)*i + j]->g, pixel[(*altura)*i + j]->b, i, j);
        }
    }

    printf("PIXEL 1: R: %d G: %d B: %d\n", pixel[0]->r, pixel[0]->g, pixel[0]->b);

    fclose(imagem);
}

int main(int argc, char *argv[]){

    int i, j; // Variáveis auxiliares

    matriz_pixel *pixel;
    int max, largura, altura; // Variáveis dos atributos da imagem PPM
    char code[3];

    ler_imagem(&pixel, &largura, &altura, &max, code, argv);

    escrever_variaveis(&pixel, largura, altura, max, code);

    free(pixel);

    return 0;
}

 

O que acontece agora é que ele não está lendo os valores do arquivo e guardando nas variáveis. Na verdade, quando executa, ele lê os três primeiros valores, ou seja, o primeiro pixel, sendo os valores R, G, B, e guarda na primeira posição do vetor, que contém a struct matriz_pixel. E logo depois ele para de funcionar. Então eu creio que é algum problema com a alocação talvez, já que ele guarda os valores apenas na posição 0 do vetor e não nas outras.

 

P.S.: Quando você falou que para manipular o vetor, teria que ser [i*j], acredito eu que teria que ser [10*i + j]. Pois apenas multiplicando o valor de i*j, você teria um problema na linha e na coluna 0, onde todos os valores dariam na posição 0 do vetor.

 

Edit 1: Eu testei guardando apenas na posição 0 do vetor e realmente está funcionando desse jeito, ou seja, os valores não estão sendo guardados apenas dentro da função, mas posso usá-los em outras funções também. O problema é que ele está alocando apenas a posição 0 do vetor e não as demais.

Link para o comentário
Compartilhar em outros sites

Abrir essa bagaça é tão simples como fazer isso

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

typedef struct Dados {
    char tipo[3];
    int nPixels;
    int nLinhas;
    int tomMax;
} Dados;

typedef struct Pixel {
    int r, g, b;
} Pixel;

FILE* fileOpen( char* name, char* mode);                    //Abre arquivo  
void allocate_mem(Pixel*** arr, int nLinhas, int nPixels); //Reserva memoria
void deallocate_mem(Pixel*** arr, int nLinhas);           //Libera memoria

//Ler imagem
void lerImagem( FILE *file, Dados *d, Pixel ***pImagem ){
    //Usamos uma só instrução para ler as 3 linhas(ja temos o cabeçalho)
    fscanf ( file, "%s\n%d %d\n%d\n", d->tipo, &d->nPixels, &d->nLinhas, &d->tomMax );
    
    //Alocaremos memoria
    allocate_mem(pImagem,d->nLinhas, d->nPixels);
    
    if (pImagem == NULL) { printf("falhou"); return; }
    
    //Ler arquivo
    int i, j;
    for (i=0; i<d->nLinhas; i++ ) {
        for (j=0; j<d->nPixels ; j++) {
            fscanf(file, "%d %d %d", &(*pImagem)[i][j].r, &(*pImagem)[i][j].g, &(*pImagem)[i][j].b );
        }
    }
}



int main() {
    //Uma só linha para abrir o arquivo em main(mais fácil entender)
    FILE* file = fileOpen("1.ppm", "r");
    
    //Se o arquivo abriu chegamos a esse ponto, agora vamos declarar as variaveis que usaremos, não há lógica criar variaveis sem ter um arquivo que ler.
    Dados dados; //Dados do cabeçalho com o tamanho da imagem entre outras coisas. Usaremos para fazer a reserva
    Pixel **imagem=NULL; //Ponteiro que apontará a nossa matriz.
    int i, j;


    //Ler arquivo
    lerImagem(file, &dados, &imagem);
    
    //mandamos os dados para um arquivo
    fclose(file); //fechamos o ponteiro a arquivo anterior para reaproveitar o ponteiro para um novo arquivo
    
    file = fileOpen("saida.ppm", "w"); //abrimos um novo arquivo em modo escritura
    
    //gravamos o cabeçalho
    fprintf ( file, "%s\n%d %d\n%d\n", dados.tipo, dados.nPixels, dados.nLinhas, dados.tomMax );
    
    //gravamos a matriz
    for (i=0; i<dados.nLinhas; i++ ) {
        for (j=0; j<dados.nPixels ; j++) {
            fprintf(file, "%3d %3d %3d\t",   imagem[i][j].r,  imagem[i][j].g,  imagem[i][j].b);
        }
    }
    
    fclose(file);
    deallocate_mem(&imagem, dados.nLinhas); //Liberar memoria
    return 0;
}

FILE* fileOpen( char* name, char* mode){
    FILE* file = fopen( name, mode);			
    if ( file == NULL ){
        perror("Erro: ");
        getchar();
        exit(1);							
    }
    return file; 
}

//Reserva memoria
void allocate_mem(Pixel*** arr, int nLinhas, int nPixels){
  *arr = (Pixel**)malloc(nLinhas*sizeof(Pixel*));
  for(int i=0; i<nLinhas; i++)
    (*arr)[i] = (Pixel*)malloc(nPixels*sizeof(Pixel));
}   

//Libera memoria
void deallocate_mem(Pixel*** arr, int nLinhas){
    for (int i = 0; i < nLinhas; i++)
        free((*arr)[i]);
    free(*arr); 
}

 

Como falei... ponteiros triplos ;)
 

adicionado 0 minutos depois

Prove abrir o arquivo de saída. Deve ver a mesma imagem que o de entrada.

Link para o comentário
Compartilhar em outros sites

Deu certo.

 

Na verdade eu só mudei essa parte:

 

for(i = 0; i < *altura; i++){
        for(j = 0; j < *largura; j++){
            fscanf(imagem, "%d", &pixel[(*altura)*i + j]->r);
            fscanf(imagem, "%d", &pixel[(*altura)*i + j]->g);
            fscanf(imagem, "%d", &pixel[(*altura)*i + j]->b);
            printf("PIXEL 1: R: %d G: %d B: %d  (%d) (%d)\n", pixel[(*altura)*i + j]->r, pixel[(*altura)*i + j]->g, pixel[(*altura)*i + j]->b, i, j);
        }
    }

para isso:

 

for(i = 0; i < *altura; i++){
        for(j = 0; j < *largura; j++){
            fscanf(imagem, "%d", &(*pixel)[(*altura)*i + j].r);
            fscanf(imagem, "%d", &(*pixel)[(*altura)*i + j].g);
            fscanf(imagem, "%d", &(*pixel)[(*altura)*i + j].b);
            printf("PIXEL 1: R: %d G: %d B: %d  (%d) (%d)\n", (*pixel)[(*altura)*i + j].r, (*pixel)[(*altura)*i + j].g, (*pixel)[(*altura)*i + j].b, i, j);
        }
}

E o printf eu mudei do mesmo jeito:

 

De:

pixel[(*altura)*i + j]->r

Para:

(*pixel)[(*altura)*i + j].r

E deu certo dessa forma. Antes eu usei o debug e estava dando "Segmentation Fault". Mas pra mim essas 2 formas são equivalentes, não sei o porque da primeira dar erro e a segunda não. 

De qualquer forma, muito obrigado pela ajuda. :)

Link para o comentário
Compartilhar em outros sites

Visitante
Este tópico está impedido de receber novas respostas.

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