[C] Problema doppio puntatore

Forum dedicato alla programmazione.

Moderatore: Staff

Regole del forum
1) Citare in modo preciso il linguaggio di programmazione usato.
2) Se possibile portare un esempio del risultato atteso.
3) Leggere attentamente le risposte ricevute.
4) Scrivere i messaggi con il colore di default, evitare altri colori.
5) Scrivere in Italiano o in Inglese, se possibile grammaticalmente corretto, evitate stili di scrittura poco chiari, quindi nessuna abbreviazione tipo telegramma o scrittura stile SMS o CHAT.
6) Appena registrati è consigliato presentarsi nel forum dedicato.

La non osservanza delle regole porta a provvedimenti di vari tipo da parte dello staff, in particolare la non osservanza della regola 5 porta alla cancellazione del post e alla segnalazione dell'utente. In caso di recidività l'utente rischia il ban temporaneo.
Rispondi
elvis
Linux 0.x
Linux 0.x
Messaggi: 53
Iscritto il: lun 27 set 2010, 10:35
Slackware: 13.1 x64
Desktop: KDE 4.5.2
Distribuzione: Opensuse 11.3 x86

[C] Problema doppio puntatore

Messaggio da elvis »

Salve, non riesco a uscire da questo problema: ho una funzione che deve restituire una matrice, tramite un doppio puntatore, al main che dovrà poi stamparla.

Il codice è questo:

Codice: Seleziona tutto

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

int **prova(int N); // prototipo funzione

int main()
{
   int NN = 4, i, j;  // NN è il numero numero di righe-colonne della matrice quadrata NNxNN
   
   int **y;
   
   y = prova(NN);
   
   printf("**y = %d *(*y + 2) = %d\n", **y, *(*y + 2));  // Qui **y è ok, e mi stampa il valore esatto a cui punta
   
   printf("Stampa matrice dentro il main:\n");	
   for (i = 0; i < NN; i++) {                               
      
      for(j = 0; j < NN; j++) printf("%3d ", *(*y + NN*i + j));   // Qui invece è come se **y perdesse l'indirizzo che contiene come valore..
      
      printf("\n");
   }           
   return 0;
}

int **prova(int N)
{
   int i, j, t = 2;
   
   int *Q = malloc((N*N)*sizeof(int));   // alloca la memoria per la matrice NxN
      
   int **M = &Q;
   
   for (i = 0; i < N; i++) {       // riempie la matrice con valori di prova (cioè t++)
      
      for(j = 0; j < N; j++)    
	 
	 *(*M + N*i + j) = t++;
   }
   
   printf("Stampa matrice dentro la funzione:\n");	
   for (i = 0; i < N; i++) { 
      
      for(j = 0; j < N; j++) printf("%3d ", *(*M + N*i + j));
      
      printf("\n");
   }    
   
   int *PP = &M[0][0];   // Puntatori ausiliari
   
   int **G = &PP;
   
   return G; 
}
L'output a schermo è il seguente:
bash-4.1$ a.out
Stampa matrice dentro la funzione:
2 3 4 5
6 7 8 9
10 11 12 13
14 15 16 17
**y = 2 *(*y + 2) = 4
Stampa matrice dentro il main:
Segmentation fault
bash-4.1$
Come potete vedere, è come se entrando nei due for nel main, **y perdesse l'indirizzo a cui punta, mentre il printf precedente stampa correttamente i due valori (0,0) e (0,2) della matrice "ricevuta".
Suggerimenti?

Avatar utente
414N
Iper Master
Iper Master
Messaggi: 2922
Iscritto il: mer 13 feb 2008, 16:19
Slackware: 15.0
Kernel: 5.15.19
Desktop: KDE5
Località: Bulagna
Contatta:

Re: [C] Problema doppio puntatore

Messaggio da 414N »

elvis ha scritto:

Codice: Seleziona tutto

  [...]
   int *PP = &M[0][0];   // Puntatori ausiliari
   
   int **G = &PP;
   
   return G; 
}
A naso direi che l'errore è proprio qua.
Nella funzione che crea la matrice hai aggiunto queste due righe per "dilettarti" con i puntatori, assegnando prima a PP l'indirizzo del primo elemento della matrice, poi a G l'indirizzo del puntatore PP, che è una variabile locale alla funzione il cui contenuto è una copia dell'indirizzo del primo elemento della matrice. Per finire, restituisci al main G invece di M, come sarebbe logico presupporre. Come certamente saprai, all'uscita da una funzione tutte le variabili locali usate al suo interno cessano di esistere (o meglio, verranno sovrascritte nei prossimi accessi allo stack da parte di una qualsiasi funzione), quindi dall'esterno (dal main, in questo caso) non puoi usare puntatori a variabili locali di funzioni.
Per risolvere il tuo problema, ho modificato nel seguente modo il tuo codice:
  • Ho reso globali il puntatore alla matrice e un puntatore temporaneo usato per l'allocazione dinamica (e basta). In questo modo, non corri il rischio di andare a puntare a roba che rischia di scomparire nel cambiamento di scope.
  • Ho aggiunto un controllo alla malloc. Bisogna SEMPRE controllare che l'allocazione sia andata a buon fine.

Codice: Seleziona tutto

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

int **M = NULL;
int *temp = NULL;

void prova(int N); // prototipo funzione

int main()
{
        int NN = 4, i, j;  // NN è il numero numero di righe-colonne della matrice quadrata NNxNN

        prova(NN);

        printf("**M = %d *(*M + 2) = %d\n", **M, *(*M + 2));  // Qui **M è ok, e mi stampa il valore esatto a cui punta

        printf("Stampa matrice dentro il main:\n");   
        for (i = 0; i < NN; i++) {                               

                for(j = 0; j < NN; j++) printf("%3d ", *(*M + NN*i + j));   // Qui invece è come se **M perdesse l'indirizzo che contiene come valore..

                printf("\n");
        }           
        return 0;
}

void prova(int N)
{
        int i, j, t = 2;

        temp = malloc((N*N)*sizeof(int));   // alloca la memoria per la matrice NxN

        if ( temp == NULL){
                fprintf (stderr, "Errore nell'allocazione della matrice nello heap.\n");
                exit (1);
        }

        M = &temp;

        for (i = 0; i < N; i++) {       // riempie la matrice con valori di prova (cioè t++)

                for(j = 0; j < N; j++)   

                        *(*M + N*i + j) = t++;
        }

        printf("Stampa matrice dentro la funzione:\n");   
        for (i = 0; i < N; i++) {

                for(j = 0; j < N; j++) printf("%3d ", *(*M + N*i + j));

                printf("\n");
        }   

        int *PP = &M[0][0];   // Puntatori ausiliari

        int **G = &PP;

}
Per finire, un consiglio: per non perderti con tutti questi puntatori, ti conviene usare delle typedef per astrarre un po' i tipi.
Nel caso di matrici, potresti pensare di usare delle struct per ospitare, oltre alla matrice stessa, informazioni quali il numero di righe e colonne (in modo da generalizzare e poter trattare indifferentemente matrici rettangolari), il determinante ecc.

Avatar utente
Barone Rosso
Linux 0.x
Linux 0.x
Messaggi: 58
Iscritto il: mer 15 set 2010, 8:11
Distribuzione: ArchLinux
Contatta:

Re: [C] Problema doppio puntatore

Messaggio da Barone Rosso »

Codice: Seleziona tutto

int *Q = malloc((N*N)*sizeof(int));   // alloca la memoria per la matrice NxN
      
   int **M = &Q;
:shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock:

Hai allocato un vettore e pretendi di usarlo come un vettore di puntatori?
Mi sembra ovvio che ti vada in segmentation fault!

se lo vuoi usare come matrice NxN; dati
i, j = 0,1,2, ... N-1

Codice: Seleziona tutto

a = Q[i*N+j]
Q[i*N+j] = b
Se vuoi un vettore di vettori, la allocazione la devi fare in questo modo:

Codice: Seleziona tutto

int **M = malloc((N)*sizeof(int*));
for ( i=0 ; i<N ; i++ ){
    M[i] = malloc((N)*sizeof(int));
}

M[i][j] = a ;
Chiaro?

elvis
Linux 0.x
Linux 0.x
Messaggi: 53
Iscritto il: lun 27 set 2010, 10:35
Slackware: 13.1 x64
Desktop: KDE 4.5.2
Distribuzione: Opensuse 11.3 x86

Re: [C] Problema doppio puntatore

Messaggio da elvis »

414N ha scritto: A naso direi che l'errore è proprio qua.
Nella funzione che crea la matrice hai aggiunto queste due righe per "dilettarti" con i puntatori, assegnando prima a PP l'indirizzo del primo elemento della matrice, poi a G l'indirizzo del puntatore PP, che è una variabile locale alla funzione il cui contenuto è una copia dell'indirizzo del primo elemento della matrice. Per finire, restituisci al main G invece di M, come sarebbe logico presupporre. Come certamente saprai, all'uscita da una funzione tutte le variabili locali usate al suo interno cessano di esistere (o meglio, verranno sovrascritte nei prossimi accessi allo stack da parte di una qualsiasi funzione), quindi dall'esterno (dal main, in questo caso) non puoi usare puntatori a variabili locali di funzioni.
Per risolvere il tuo problema, ho modificato nel seguente modo il tuo codice:
  • Ho reso globali il puntatore alla matrice e un puntatore temporaneo usato per l'allocazione dinamica (e basta). In questo modo, non corri il rischio di andare a puntare a roba che rischia di scomparire nel cambiamento di scope.
  • Ho aggiunto un controllo alla malloc. Bisogna SEMPRE controllare che l'allocazione sia andata a buon fine.

Codice: Seleziona tutto

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

int **M = NULL;
int *temp = NULL;

void prova(int N); // prototipo funzione

int main()
{
        int NN = 4, i, j;  // NN è il numero numero di righe-colonne della matrice quadrata NNxNN

        prova(NN);

        printf("**M = %d *(*M + 2) = %d\n", **M, *(*M + 2));  // Qui **M è ok, e mi stampa il valore esatto a cui punta

        printf("Stampa matrice dentro il main:\n");   
        for (i = 0; i < NN; i++) {                               

                for(j = 0; j < NN; j++) printf("%3d ", *(*M + NN*i + j));   // Qui invece è come se **M perdesse l'indirizzo che contiene come valore..

                printf("\n");
        }           
        return 0;
}

void prova(int N)
{
        int i, j, t = 2;

        temp = malloc((N*N)*sizeof(int));   // alloca la memoria per la matrice NxN

        if ( temp == NULL){
                fprintf (stderr, "Errore nell'allocazione della matrice nello heap.\n");
                exit (1);
        }

        M = &temp;

        for (i = 0; i < N; i++) {       // riempie la matrice con valori di prova (cioè t++)

                for(j = 0; j < N; j++)   

                        *(*M + N*i + j) = t++;
        }

        printf("Stampa matrice dentro la funzione:\n");   
        for (i = 0; i < N; i++) {

                for(j = 0; j < N; j++) printf("%3d ", *(*M + N*i + j));

                printf("\n");
        }   

        int *PP = &M[0][0];   // Puntatori ausiliari

        int **G = &PP;

}
Per finire, un consiglio: per non perderti con tutti questi puntatori, ti conviene usare delle typedef per astrarre un po' i tipi.
Nel caso di matrici, potresti pensare di usare delle struct per ospitare, oltre alla matrice stessa, informazioni quali il numero di righe e colonne (in modo da generalizzare e poter trattare indifferentemente matrici rettangolari), il determinante ecc.
Mi sono dimenticato di specificare che non posso modificare l'intestazione della funzione (in questo caso renderla una f. void), deve restituire per forza un doppio puntatore.
Provando a levare i due puntatori prima del return, e ritornando M, l'errore (ovviamente?) rimane.

Ok per verifica malloc e typedef.

Barone Rosso ha scritto:

Codice: Seleziona tutto

int *Q = malloc((N*N)*sizeof(int));   // alloca la memoria per la matrice NxN
      
   int **M = &Q;
:shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock: :shock:

Hai allocato un vettore e pretendi di usarlo come un vettore di puntatori?
Mi sembra ovvio che ti vada in segmentation fault!

se lo vuoi usare come matrice NxN; dati
i, j = 0,1,2, ... N-1

Codice: Seleziona tutto

a = Q[i*N+j]
Q[i*N+j] = b
Se vuoi un vettore di vettori, la allocazione la devi fare in questo modo:

Codice: Seleziona tutto

int **M = malloc((N)*sizeof(int*));
for ( i=0 ; i<N ; i++ ){
    M[i] = malloc((N)*sizeof(int));
}

M[i][j] = a ;
Chiaro?
Non del tutto, non comprendo cosa significhi quell' a = Q[..] = b.
Stai semplicemente dicendo che dopo aver fatto quel malloc "indentato" (con quel for), dentro M[j] posso metterci gli interi che mi pare?

EDIT: comunque gestendo il malloc in questo modo funziona, grazie!

Avatar utente
Barone Rosso
Linux 0.x
Linux 0.x
Messaggi: 58
Iscritto il: mer 15 set 2010, 8:11
Distribuzione: ArchLinux
Contatta:

Re: [C] Problema doppio puntatore

Messaggio da Barone Rosso »

elvis ha scritto: Non del tutto, non comprendo cosa significhi quell' a = Q[..] = b.
Stai semplicemente dicendo che dopo aver fatto quel malloc "indentato" (con quel for), dentro M[j] posso metterci gli interi che mi pare?

EDIT: comunque gestendo il malloc in questo modo funziona, grazie!


Caso 1)
Q = malloc( N*N , sizeof(int) ) ;


poi usi Q in questo modo.
ad esempio:
Q[i*N+j] = 23 ;

Usi un vettore come se fosse una matrice, si tratta di una tecnica molto diffusa ed in genere preferita alla costruzione di un vettore di vettori.

Caso 2)
M ... è un vettore di int* (puntatori a int).
mentre M[j] ... è la tua matrice, che di fatto è un vettore di vettori.

Consiglio: se vuoi fare un matrice ... usa il metodo 1.

elvis
Linux 0.x
Linux 0.x
Messaggi: 53
Iscritto il: lun 27 set 2010, 10:35
Slackware: 13.1 x64
Desktop: KDE 4.5.2
Distribuzione: Opensuse 11.3 x86

Re: [C] Problema doppio puntatore

Messaggio da elvis »

Molto interessante, grazie!

Però strano, forse mi è sfuggito ma nel Deitel non ho mai visto citato il caso 1, devo vedere se invece ci sia sul Kernighan...

Avatar utente
Barone Rosso
Linux 0.x
Linux 0.x
Messaggi: 58
Iscritto il: mer 15 set 2010, 8:11
Distribuzione: ArchLinux
Contatta:

Re: [C] Problema doppio puntatore

Messaggio da Barone Rosso »

elvis ha scritto:Molto interessante, grazie!

Però strano, forse mi è sfuggito ma nel Deitel non ho mai visto citato il caso 1, devo vedere se invece ci sia sul Kernighan...
Non penso che lo citino. Il caso 1 è solo un vettore con una strategia di indicizzazione.
In un testo più specifico a problematiche di ingegneria del software (per applicazioni numeriche) lo citano di sicuro.

Tecnicamente parlando il caso 2 è una matrice.

Avatar utente
414N
Iper Master
Iper Master
Messaggi: 2922
Iscritto il: mer 13 feb 2008, 16:19
Slackware: 15.0
Kernel: 5.15.19
Desktop: KDE5
Località: Bulagna
Contatta:

Re: [C] Problema doppio puntatore

Messaggio da 414N »

elvis ha scritto: Provando a levare i due puntatori prima del return, e ritornando M, l'errore (ovviamente?) rimane.
Il problema è che tenti di ritornare l'indirizzo di una variabile locale di una funzione, e questo ti darà sempre problemi. Se non usi variabili globali, fai fatica ad uscirne.
Adottando una strategia simile a quella che ti ho presentato, puoi risolvere ritornando direttamente &temp (se la malloc è andata a buon fine), altrimenti NULL se non è andata a buon fine. Poi gestisci la situazione nel main (in pratica, incapsuli la malloc). In questo modo, la signature della funzione (che hai detto essere non modificabile) non cambia.
Barone Rosso ha scritto: Caso 1)
Q = malloc( N*N , sizeof(int) ) ;
poi usi Q in questo modo.
ad esempio:
Q[i*N+j] = 23 ;

Usi un vettore come se fosse una matrice, si tratta di una tecnica molto diffusa ed in genere preferita alla costruzione di un vettore di vettori.
Alla fine è proprio quello che succede nei for innestati sia nel main che nella funzione, se ci guardi bene.
Certo, con le [] e un passaggio di puntamento in meno sarebbe stato un po' più leggibile e meno contorto...

Avatar utente
Calzo
Linux 1.x
Linux 1.x
Messaggi: 112
Iscritto il: sab 6 ott 2007, 22:21
Slackware: 10.2 | 13
Desktop: Fluxbox | KDE
Località: MN

Re: [C] Problema doppio puntatore

Messaggio da Calzo »

Secondo me invece il problema è che la matrice definita come

Codice: Seleziona tutto

int **M;
è un vettore di vettori, tant'è che poi la indirizzi con M[x][y], ossia l'elemento y puntato dal vettore di interi M[x].
Io ho provato a scrivere questo esempio e funziona (spero ti sia di aiuto):

Codice: Seleziona tutto

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

#define DIM	4

int **genera_matrice(int N)
{
    int i,j;
    int **ret = malloc(N*sizeof(int));

    for (i=0; i<N; i++)
        ret[i] = malloc(N*sizeof(int));
    
    // Riempio la matrice e la stampo
    for (i=0; i<N; i++)
    {
        for (j=0; j<N; j++)
        {
        ret[i][j] = i*j;
        printf("%d\t", ret[i][j]);
        }
        printf("\n");
    }

    return ret;
}

int main(void)
{
    int i,j;
    int **M;
    
    M = genera_matrice(DIM);

    printf("\n");
    for (i=0; i<DIM; i++)
    {
        for (j=0; j<DIM; j++)
            printf("%d\t", M[i][j]);
        printf("\n");
    }
}
In altre parole la funzione genera_matice() (l'equivalente della tua funzione prova()), prima genera il primo vettore, il quale è un vettore di puntatori, poi crea tutti gli altri vettori.
Il mio programma è solo di esempio: non fa controlli sulla mamoria (come giustamente ti hanno suggerito prima) e non libera la memoria in uscita (che ovviemente dovrà liberare tutti i vettori e poi la matrice finale).

Bye

Rispondi