Oppure

Loading
07/04/17 17:00
AldoBaldo
Per cominciare, un saluto a te che leggi. Indi...

Una cosa che mi capita di usare piuttosto spesso nei miei programmini in C sono le matrici bidimensionali, quelle sul genere di matrice[nRighe][nColonne], per intendersi. Per varie ragioni trovo a volte utile allocarle dinamicamente, ed è un compito che dopo un po' diventa noioso. Per questo ho pensato di provare a mettere insieme una funzione da poter copia-e-incollare ogni qualvolta mi dovesse servire. Dovendo fare in modo che sia adattabile a qualsiasi tipo di dati, ho pensato di fare in modo che restituisca un void**, così:

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

typedef struct struct_esempio_t {
    char str1[24];
    char str2[32];
} esempio_t;

void **crea_matrice(
    uint32_t dim_el, uint32_t n_righe, uint32_t n_colonne );
void distruggi_matrice( void ***matrice, uint32_t n_righe );

int main() {
    const uint32_t kQRighe=4, kQColonne=3; // dimensioni della matrice

    esempio_t **matrice =
        (esempio_t**) crea_matrice( sizeof(esempio_t), kQRighe, kQColonne );

    if( matrice != NULL ) {
        uint32_t r, c; // contatori
        char buff[32]; // ausialiario, per comporre le stringhe d'esempio

        // "popola" la matrice
        for( r=0; r<kQRighe; ++r ) {
            for( c=0; c<kQColonne; ++c ) {
                sprintf( buff, "(r%dc%d) str1", r, c );
                strcpy( matrice[r][c].str1, buff );
                sprintf( buff, "(r%dc%d) str2", r, c );
                strcpy( matrice[r][c].str2, buff );
            }
        }

        // mostra il contenuto della matrice
        for( r=0; r<kQRighe; ++r ) {
            for( c=0; c<kQColonne; ++c ) {
                printf( "riga %d, colonna %d: %s, %s\n",
                        r, c, matrice[r][c].str1, matrice[r][c].str2 );
            }
        }

        // libera la matrice e ne annulla il puntatore
        distruggi_matrice( (void***) &matrice, kQRighe );
    }
    return 0;
}

void **crea_matrice(
    uint32_t dim_el, uint32_t n_righe, uint32_t n_colonne ) {

    // alloca i puntatori alle righe, tutti NULL
    void **matrice = calloc( n_righe, sizeof(void*) );

    if( matrice != NULL ) { // se l'allocazione e' riuscita...
        uint32_t r; // contatore

        for( r=0; r<n_righe; ++r ) { // per ogni riga
            // alloca in blocco tutte le colonne sulla riga
            matrice[r] = calloc( n_colonne, dim_el*n_colonne );

            if( matrice[r] == NULL ) { // se l'allocazione NON e' riuscita...
                distruggi_matrice( &matrice, r );
                break;
            }
        }
    }

    return matrice; // NULL in caso d'errore
}

void distruggi_matrice( void ***matrice, uint32_t n_righe ) {
    if( matrice != NULL ) {
        void **m = *matrice; // per praticita' di lettura

        if( m != NULL ) { // se la matrice esiste...
            uint32_t r; // contatore delle righe

            for( r=0; r<n_righe; ++r ) // per ogni riga della matrice...
                free( m[r] ); // libera in blocco tutte le colonne sulla riga

            free( m ); // libera l'array dei puntatori alle righe
            *matrice = NULL; // annulla il puntatore alla matrice
        }
    }
}


L'idea pare funzionare, anche se mi aspetto che qualcuno più pratico di me possa indicarmi qualche errore.

La questione che vorrei porre, però, non riguarda gli (eventuali) errori, bensì la ragione per la quale il compilatore (mingw) mi impone di effettuare il cast di tipo da void** a esempio_t** e viceversa. So che quel cast sarebbe obbligatorio (almeno tanto quanto è deprecato) nel caso di un programma in C++, ma il mio esempio è un programmino in C... in C, un puntatore a doppia indirezione di tipo void** non dovrebbe essere liberamente "intercambiabile" con un puntatore a doppia indirezione di qualsiasi altro tipo anche senza fare il cast? void** non è utilizzabile come puntatore generico almeno quanto il void* restituito (per dire) da calloc() che può essere assegnato a un puntatore di qualsiasi tipo senza cast?

int *ptrNum = calloc( 1, sizeof(*ptrNum) ); // da void* a int* senza cast!


Dov'è la falla nel mio ragionamento? Cosa sbaglio?
Ultima modifica effettuata da AldoBaldo 07/04/17 17:02
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
07/04/17 17:07
lumo
Dovrebbe essere proprio come dici tu, prova a mettere tutto in un
#ifdef __cplusplus

#endif


È una macro che tutti i compilatori C++ definiscono (mentre quelli C no) quindi dovrebbe dirti subito se per caso stai compilando con mingw++.

P.S.: se posso fare solo un appunto al codice, i parametri della funzione li vedrei bene come size_t
aaa
07/04/17 17:13
Template
Ponendo che la funzione che hai scritto sia corretta (non ho tempo per controllarla):

Il tipo void ** non è equivalente a void *: sono entrambi puntatori, ma il primo è del tipo "puntatore ad un puntatore (che a sua volta punta a void)", mentre il secondo è del tipo "puntatore a void".
Questo vuol dire che per il tipo void ** si applica quanto statuito nelle sezioni 6.6 e 6.8 del Reference manual del linguaggio C inserito nel testo di Kernighan-Ritchie (seconda edizione, appendice A), ovvero non è possibile un cast implicito ed inoltre a seconda dell'implementazione potrebbero non essere possibili alcuni cast espliciti.

EDIT: vedesi anche la risposta data qui: stackoverflow.com/questions/25427587/…
Ultima modifica effettuata da Template 07/04/17 17:22
aaa
07/04/17 17:22
lumo
Ok avevo letto troppo velocemente, in effetti con i puntatori multipli il discorso cambia e Template dice giusto (infatti mi pareva strano che calloc nel tuo codice funzionasse e altrove no).
aaa
07/04/17 19:37
AldoBaldo
Postato originariamente da lumo:

Dovrebbe essere proprio come dici tu, prova a mettere tutto in un
#ifdef __cplusplus

#endif


Ho provato: ne deriva questo...

||=== Build: Debug in Matrice (compiler: GNU GCC Compiler) ===|
C:\Program Files\CodeBlocks\MinGW\bin\..\lib\gcc\mingw32.9.2\..\..\..\libmingw32.a(main.o):main.c:(.text.startup+0xa7)||undefined reference to `WinMain@16'|
||error: ld returned 1 exit status|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 2 second(s)) ===|


...dal che deduco che il programma venga effettivamente compilato come C e non come C++.

Ora provo a "sondare" la questione della doppia indirezione, poi "ritorno".
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
07/04/17 19:58
AldoBaldo
Postato originariamente da lumo:
se posso fare solo un appunto al codice, i parametri della funzione li vedrei bene come size_t


Puoi fare tutti gli appunti che vuoi, per me è grasso che cola!

Intendi così...

void **crea_matrice( size_t dim_el, uint32_t n_righe, uint32_t n_colonne );

... o così?

void **crea_matrice( size_t dim_el, size_t n_righe, size_t n_colonne );

(a me sembra più sensata la prima)
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
07/04/17 20:08
AldoBaldo
Ho letto l'intera pagina sulla questione del void** e direi che fa strame della mia funzione crea_matrice(). Dovrò pensare a qualcos'altro. Grazie a Template per avermi ricondotto sulla "retta via", tirandomi coi piedi per terra. Però non mi arrendo... ancora! :heehee:
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
07/04/17 20:35
AldoBaldo
Una soluzione banale potrebbe essere quella di "legare" ogni particolare funzione crea_matrice a un determinato tipo, in modo esplicito, magari apponendo il nome del tipo al nome della funzione. Con una soluzione del genere basta copiare e incollare la funzione d'esempio e effettuare una sostituzione di tutte le sottostringhe del codice che identificano il tipo in questione. Con un tipo chiamato TIPO_ES, ad esempio...

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

typedef struct struct_TIPO_ES { // TIPO_ES = tipo esempio
    int r, c;
    char str[32];
} TIPO_ES;

TIPO_ES **crea_matrice_TIPO_ES( uint32_t qRighe, uint32_t qColonne );
void distruggi_matrice_TIPO_ES( TIPO_ES ***matrice, uint32_t qRighe );

int main() {
    const uint32_t kQRighe=4, kQColonne=3; // dimensioni della matrice

    TIPO_ES **matrice = crea_matrice_TIPO_ES( kQRighe, kQColonne );

    if( matrice != NULL ) {
        uint32_t r, c; // contatori
        char buff[32]; // ausialiario, per comporre le stringhe d'esempio

        // "popola" la matrice
        for( r=0; r<kQRighe; ++r ) {
            for( c=0; c<kQColonne; ++c ) {
                matrice[r][c].r = r;
                matrice[r][c].c = c;
                sprintf( buff, "r%dc%d", matrice[r][c].r, matrice[r][c].c );
                strcpy( matrice[r][c].str, buff );
            }
        }

        // mostra il contenuto della matrice
        for( r=0; r<kQRighe; ++r ) {
            for( c=0; c<kQColonne; ++c ) {
                printf( "TIPO_ES.r = %d   TIPO_ES.c = %d   TIPO_ES.str = %s\n",
                        matrice[r][c].r, matrice[r][c].c, matrice[r][c].str );
            }

            printf( "\n" );
        }

        // libera la matrice e ne annulla il puntatore
        distruggi_matrice_TIPO_ES( &matrice, kQRighe );
    }
    return 0;
}

TIPO_ES **crea_matrice_TIPO_ES( uint32_t qRighe, uint32_t qColonne ) {
    // alloca i puntatori alle righe, tutti NULL
    TIPO_ES **matrice = calloc( qRighe, sizeof(TIPO_ES*) );

    if( matrice != NULL ) { // se l'allocazione e' riuscita...
        uint32_t r; // contatore

        for( r=0; r<qRighe; ++r ) { // per ogni riga
            // alloca in blocco tutte le colonne sulla riga
            matrice[r] = calloc( qColonne, sizeof(*matrice[r])*qColonne );

            if( matrice[r] == NULL ) { // se l'allocazione NON e' riuscita...
                distruggi_matrice_TIPO_ES( &matrice, r );
                break;
            }
        }
    }

    return matrice; // NULL in caso d'errore
}

void distruggi_matrice_TIPO_ES( TIPO_ES ***matrice, uint32_t qRighe ) {
    if( matrice != NULL ) {
        TIPO_ES **m = *matrice; // per praticita' di lettura

        if( m != NULL ) { // se la matrice esiste...
            uint32_t r; // contatore delle righe

            for( r=0; r<qRighe; ++r ) // per ogni riga della matrice...
                free( m[r] ); // libera in blocco tutte le colonne sulla riga

            free( m ); // libera l'array dei puntatori alle righe
            *matrice = NULL; // annulla il puntatore alla matrice
        }
    }
}


In questo modo non serve neanche comunicare alla funzione le dimensioni del tipo dei dati, perché può ricavarle da sola tramite sizeof. Inoltre non rimane traccia di cast di alcun genere, che non è certo un male. Suggerimenti?

P.S. Se solo il C avesse contezza dell'overload di funzioni... magari in qualche sua incarnazione più recente di quella che conosco io (ormai almeno ventennale) si può usare l'overload o è roba solo per il C++ com'era "ai miei tempi"?
Ultima modifica effettuata da AldoBaldo 07/04/17 20:40
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.