Allocazione dinamica e funzioni...

menale1
Carissimi ragazzi c'è un dubbio che mi assale circa la compilazione di un piccolo programmino che desidererei condividere con voi.
La docente di Calcolo Numerico ha dato in consegna la scrittura di un programma che eseguisse il prodotto matrice matrice, utilizzando per questa stessa operazione una function, con il tutto in allocazione dinamica. Prima di esprimere il mio dubbio a riguardo, preferisco mostrarvi il codice
#include<stdlib.h>
#include<stdio.h>
void prod(double **a, double **b, double **c, int n);
int main (void){
    double **a, **b, **c;
    int i, j, n;
    printf("Inserisci dimensione\n");
    scanf("%d", &n);
    a=(double**)calloc(n,sizeof(double));
    b=(double**)calloc(n,sizeof(double));
    c=(double**)calloc(n,sizeof(double));
    for(i=0;i<n;i++){
                     a[i]=(double*)calloc(n,sizeof(double));
                     b[i]=(double*)calloc(n,sizeof(double));
                     c[i]=(double*)calloc(n,sizeof(double));
                     }
                     printf("Inserisci elementi matrice a\n");
                     for(i=0;i<n;i++){
                                      for(j=0;j<n;j++){
                                      printf("Inserisci elemento %d %d\n", i,j);
                                      scanf("%lf", &a[i][j]);
                                      }
                                      }
                                       printf("Inserisci elementi matrice b\n");
                     for(i=0;i<n;i++){
                                      for(j=0;j<n;j++){
                                      printf("Inserisci elemento %d %d\n", i,j);
                                      scanf("%lf", &b[i][j]);
                                      }
                                      }
                                      prod(a ,b ,c, n);
                                      printf("La matrice risultante è\n");
                                      for(i=0;i<n;i++){
                                                       for(j=0;j<n;j++){
                                                                        printf("L'elemento %d %d\n" ,i,j);
                                                                        printf("%lf\n", c[i][j]);
                                                                        }
                                                                        }
                                                                        for(i=0;i<n;i++){
                                                                                         free(a[i]);
                                                                                         free(b[i]);
                                                                                         free(c[i]);
                                                                                         }
                                                                                         free(a);
                                                                                         free(b);
                                                                                         free(c);
                                                                                         system("PAUSE");
                                                                                         return 0;
                                                                                         }
                                                                                         
void prod(double **a, double **b, double **c, int n);
void prod(double **a, double **b, double **c, int n){
     int i, j, k;
     for(i=0;i<n;i++){
                      for(j=0;j<n;j++){
                                       c[i][j]=0;
                                       for(k=0;k<n;k++){
                                                       c[i][j]+=a[i][k]*b[k][j];
                                                       }
                                                       }
                                                       }
                                                       }
                                      

Nella prima parte del programma ho allocato dinamicamente le variabili dopo di che son passato alla realizzazione della funzione. Ciò che mi preme è il momento in cui tiro in ballo la function nel corso del programma, ossia
 prod(a ,b ,c, n)
. Inizialmente pensavo dovessi far riferimento agli indirizzi di a,b e c ossia che dovessi scrivere qualcosa del genere
 prod(&a ,&b ,&c, n)
, ma tale soluzione, in fase di compilazione, portava in errore, pertanto togliendo il simbolo "&", ossia non facendo più riferimento agli indirizzi delle tre matrici, il programma ha dato buoni esiti.
L'unica spiegazione che sono stato in grado di darmi è che per come ho costruito la function, automaticamente questa "attende" in entrata dei puntatori (di puntatori in tal caso), senza dover specificare alcun indirizzo.
In attesa di vostre risposte all'amletico dubbio, ringrazio sentitamente per la collaborazione

Risposte
lordb
Ciao, innanzi tutto hai scritto due volte la dichiarazione:
void prod(double **a, double **b, double **c, int n);

ed è ridondante.

Venendo al tuo problema: la funzione riceve tre "doppi puntatori" e un intero.

In C ogni cosa è passata per Valore. Poichè a volte può essere poco conveniente copiare per valore alcuni dati (di dimensioni molto grandi) è stato inventato il passaggio per riferimento, che è sempre un passaggio per valore, ma vengono copiati dei puntatori (che contengono gli indirizzi delle celle di memoria dove si trovano le informazioni). E' stato scelto di passare sempre per riferimento la struttura dati Array e quindi non devi estrarre nessun indirizzo al momento del passaggio alla funzione.

P.s. (in realtà quello che ti ho appena detto non è del tutto vero ma è quello che solitamente viene detto a chi si avvicina al linguaggio C per la prima volta).
Il trucco si cela dietro questa frase :
E' stato scelto di passare sempre per riferimento la struttura dati Array e quindi non devi estrarre nessun indirizzo al momento del passaggio alla funzione.


Il passaggio per riferimento degli Array in realtà non è stata per niente una scelta facoltativa ma è stata resa obbligata dall'implementazione degli Array per mezzo dei puntatori.
In C il nome degli array (e quindi delle matrici) non rappresentano nient'altro che un puntatore costante al primo elemento contenuto dalla "collezione" e quindi quando passi il nome alla funzione in realtà stai passando un puntatore costante.
Utilizzare l'estrattore di indirizzo sul nome degli array/matrici equivale a cercare l'indirizzo della cella di memoria del puntatore che punta al primo elemento della collezione.

Spero di essere stato chiaro

menale1
Dunque, se ho ben capito, la funzione si basa sul primo elemento del mio array "dinamico" ed in tal modo non è per nulla necessario l'indirizzo di allocazione?

lordb
La funzione si aspetta un puntatore, ergo tu le passi il nome di un array (che è un puntatore costante al primo elemento).
Se vuoi poter accedere agli elementi di un array l'unica cosa che ti serve sapere è il suo nome (puntatore al primo elemento..) e la sua lunghezza.
Estrarre l'indirizzo dal nome del vettore ti dice solo dove è allocato il puntatore (che può essere ovunque ed è un'informazione inutile): ma a te serve sapere dove sia allocata la prima cella dell'array (così sapendo dove si trova quella e sapendo la lunghezza puoi risalire a tutte le altre che compongono la collezione): orbene questa informazione è contenuta all'interno del nome stesso dell'array che come ho già detto non è altro che un puntatore.

menale1
Ok! Dunque in generale se ad una funzione devo passare un qualche cosa allocato dinamicamente le passo il nome di quell'elemento che rende il puntatore costante al primo elemento?

hamming_burst
l'allocazione dinamica è sbagliata nel codice, ed è un errore parecchio grave
    a=(double**)calloc(n,sizeof(double));
    b=(double**)calloc(n,sizeof(double));
    c=(double**)calloc(n,sizeof(double));
    for(i=0;i<n;i++){
                     a[i]=(double*)calloc(n,sizeof(double));
                     b[i]=(double*)calloc(n,sizeof(double));
                     c[i]=(double*)calloc(n,sizeof(double));
     }



prendiamo:
double **a=(double**)calloc(n,sizeof(double));

questo non fa quello che credi te. Quello che fai è allocare uno spazio di $n$ double (n*8Byte) e non $n$ puntatori ad array di double (n* grandezza puntatori dell'architettura: 4Byte per 32 bit, 8Byte per 64bit).

Non pensare sia la stessa cosa. In C questo genere di cose son permesse solo per scelte semantiche tra la scrittura degli array con l'aritmetica dei puntatori.

Se fortunato in questo caso perchè il tipo double occupa il doppio dello spazio richiesto, ma avresti un bel seg-fault in altri casi. Prova ad allorcare una matrice di char con lo stesso sistema...

Correttamente è così il codice:
double **a=(double**)calloc(n,sizeof(double*));


il for è ok.

attenzione poi all'uso massiccio della calloc(). E' utile per piccole allocazioni all'inizializzazione al valore $0$ del tipo. Per esperienza a determinate grandezze di $n$ tale funzione crasha in modo davvero elegante, e te non fai nessun controllo su cosa ci stia dentro il valore puntato che hai appena allocato, è cosa buona mettere un if sul valore di ritorno.

menale1
Anche se uso malloc devo adoperare questo comando
double **a=(double**)calloc(n,sizeof(double));
ovviamente con i cambi del caso, anzichè la strutturazione che ne ho fatto io?

menale1
Il problema è che noi questi meccanismi li stiamo apprendendo da autodidatti dal momento che la nostra docente pone assiomaticamente la conoscenza di questi meccanismi di allocazione dinamica, il che non si presenta come scusante, ma al limite come attenuante. Comunque mi pongo in attesa di risposte!

hamming_burst
"menale":
Anche se uso malloc devo adoperare questo comando
double **a=(double**)calloc(n,sizeof(double));
ovviamente con i cambi del caso, anzichè la strutturazione che ne ho fatto io?


era solo un consiglio l'utilizzo della malloc(), usa pure calloc() ma metti almeno un cotrollo formale tipo

if(!a[i]&&!b[i]&&!c[i]){
     return -1;
}


l'errore grave a cui mi riferisco io, hai compreso quale sia?

menale1
"hamming_burst":
l'errore grave a cui mi riferisco io, hai compreso quale sia?

Sìsì, è stata una distrazione nello scrivere il codice, dovevo scrivere sizeof(double*) nel primo passaggio, altrimenti la matrice allocata dinamicamente non la ottengo mica.
Per quanto concerne il discorso precedente, mi potresti dire perché possa convenire utilizzare la colloc anziché la malloc?

hamming_burst
"menale":
[quote="hamming_burst"]l'errore grave a cui mi riferisco io, hai compreso quale sia?

Sìsì, è stata una distrazione nello scrivere il codice, dovevo scrivere sizeof(double*) nel primo passaggio, altrimenti la matrice allocata dinamicamente non la ottengo mica. [/quote]
se rileggi ciò che ho scritto, il problema è che la funzione così come è scritta funziona comunque, senza darti un errore in tempo di compilazione e qui è una debolezza del Linguaggio C.
Era questo che volevo sottolinearti se invece avessi fatto una matrice di char:

char** a = malloc(sizeof(char)*dim));

while(i<dim){
        a[i]=malloc(sizeof(char)*dim));
i++;
}


tale codice produrrebbe un errore a run-time, al contrario del precedente. (sizeof(duble)=8Byte, sizeof(char)=1Byte)
In alcuni casi il compilatore aggiunge un po' di memoria intorno all'allocazione può succedere che passi inosservato tale erroe, ma ripeto che sarebbe fonte di bug, se è stata una distrazione meglio così :)


Per quanto concerne il discorso precedente, mi potresti dire perché possa convenire utilizzare la colloc anziché la malloc?

conosci la differenza tra le due funzioni?

menale1
"hamming_burst":
conosci la differenza tra le due funzioni?

Scrutando dei manuali, beh calloc inizializza a zero il mio oggetto allocato dinamicamente, mentre malloc non ha tale prassi!

hamming_burst
"menale":
[quote="hamming_burst"]conosci la differenza tra le due funzioni?

Scrutando dei manuali, beh calloc inizializza a zero il mio oggetto allocato dinamicamente, mentre malloc non ha tale prassi![/quote]
ok.
detesto quotarmi ma per risponderti:
attenzione poi all'uso massiccio della calloc(). E' utile per piccole allocazioni all'inizializzazione al valore 0 del tipo. Per esperienza a determinate grandezze di n tale funzione crasha in modo davvero elegante (pessimo), e te non fai nessun controllo su cosa ci stia dentro il valore puntato che hai appena allocato, è cosa buona mettere un if sul valore di ritorno.

sunto, se devi avere un vettore/matrice inizializzato a $0$ (o simile) allora hai due possibilità:
- valore di $n$ non troppo grande utilizza una calloc() è veloce, ma crasha per $n$ grande per questo consiglio un controllo come ho mostrato
- sempre malloc() allora una parte di memoria, per inizializzare devi fare un ciclo while con assegnazione $=0$.

se devi allocare memoria senza inizializzazione allora utilizza una malloc().


Ci sono sistemi più basso livello per grandi matrici o se c'è bisogno di grandi porzioni di memoria come memset(). Ma penso che per i tuoi scopi didattici basta la malloc o calloc.

Nota: la malloc() è utile, ma in certi casi è costosa e poco affidabile per questo esistono altri sistemi.

menale1
Ok, ti ringrazio per l'aiuto.
Un'ultima cosa, scrutando il manuale ho notato che esiste, sempre nell'ambito dell'allocazione dinamica, anche realloc la qual funzione serve per riallocare memoria. D'accordo su questo aspetto, ciò che vien da chiedere è: quando può risultare necessario il suo utilizzo?

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.