C, matrici, allocazione dinamica ed eleganza del codice.

di il
50 risposte

C, matrici, allocazione dinamica ed eleganza del codice.

Ispirato da un post di qualche ora fa dove era necessario allocare dinamicamente una matrice ho rispolverato qualche vecchio amico in unione con alcuni aspetti meno conosciuti del C.
Mi chiedo se è possibile creare/se conoscete una soluzione più elegante della seguente, sia per allocare una matrice dinamicamente, sia per utilizzarla in una funzione:

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

void getDimensions(unsigned *, unsigned *);
unsigned (*allocate(unsigned, unsigned))[];
void doSomething(unsigned, unsigned, unsigned(*)[*]);


int main()
{
   unsigned r, c;
   
   getDimensions(&r, &c);

    unsigned(*matrix)[c] = (unsigned(*)[c]) allocate(r, c);
   
	
   if(matrix)
      doSomething(r, c, matrix);
   
   free(matrix);
   
    return 0;
}



void getDimensions(unsigned *r, unsigned *c) {
   do {
    scanf("%u", r);
    scanf("%u", c);
   } while(*r == 0 || *c == 0); // unsigned -> negative values in input will be considered positive
   return;
}


unsigned (*allocate(unsigned r, unsigned c))[]
{
    return (unsigned(*)[]) malloc(r * c * sizeof(unsigned));
}


void doSomething(unsigned r, unsigned c, unsigned(*matrix)[c]) {
   
   unsigned i = 0;
   unsigned j;
   
   for(; i < r; i++)
      for(j = 0; j < c; j++)
         matrix[i][j] = j * i;

   for(i = 0; i < r; printf("%c", '\n'), i++)
      for(j = 0; j < c; j++)
         printf("%u ", matrix[i][j]);
   
   return;
}

50 Risposte

  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Non capisco perché usare i VLA array, è una feature che era stata introdotta nel C99 e resa opzionale nel C11
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    IfNotFalseTrue ha scritto:


    Non capisco perché usare i VLA array, è una feature che era stata introdotta nel C99 e resa opzionale nel C11
    Non sto usando i VLA ma i pointer-to-VLA
    E come puoi vedere, l'allocazione per una matrice si riduce ad una sola istruzione. Ma anche l'allocazione per un array di n-dimensioni si riduce sempre e comunque ad una sola istruzione. Mantenendo però la possibilità di utilizzare la sintassi comune alle matrici, tensori, ecc...
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Non metto in dubbio la bontà della soluzione, ma di fatto usa una feature che è garantita solo dal C99 e per questo non mi piace
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    IfNotFalseTrue ha scritto:


    Non metto in dubbio la bontà della soluzione, ma di fatto usa una feature che è garantita solo dal C99 e per questo non mi piace
    Non saprei se i pointer-to-VLA sono opzionali, in comune con i VLA hanno solo il nome.
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Mi chiedo se è possibile creare/se conoscete una soluzione più elegante della seguente, sia per allocare una matrice dinamicamente, sia per utilizzarla in una funzione:
    Personalmente in C (puro) opterei per questa:
    
    some_type* mat = malloc(r*c*sizeof(sometype));
    /* ...etc */
    
    senza aver bisogno di nulla che puzzi di VLA
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    shodan ha scritto:


    Mi chiedo se è possibile creare/se conoscete una soluzione più elegante della seguente, sia per allocare una matrice dinamicamente, sia per utilizzarla in una funzione:
    Personalmente in C (puro) opterei per questa:
    
    some_type* mat = malloc(r*c*sizeof(sometype));
    /* ...etc */
    
    senza aver bisogno di nulla che puzzi di VLA
    Ma poi come la usi la matrice? Non puoi usare la sintassi normale
    Inoltre dato che alla fine si tratta di un puntatore, e non di allocare memoria a runtime sullo stack, non c'è problema nell'usare un pointer-to-VLA
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Rispetto ai vantaggi che si hanno, il mancato uso di [][] lascia il tempo che trova. Al limite ci si fa una funzioncina che trova l'elemento cercato tramite la classica formuletta:
    Mat[r] [c] = Mat[Cols * r + c]
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Di quali vantaggi parli? Comunque l'eleganza sta proprio in questo
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Se la matrice è "simulata" da un array (dinamico o meno), la copia di due matrici può essere fatta tramite una memcpy, un azzeramento tramite una memset; in pratica tutto quello che puoi fare su un array. Il vantaggio principale, però, è che l'allocazione avviene tutta in sol colpo senza dover controllare se ogni singola allocazione di ogni singola riga sia fallita o no (non sarebbe bello che proprio sull'ultima riga ci sia un problema di allocazione e dover così liberare tutto il resto).
    Inoltre senza VLA il codice è 100% compatibile con compilatori C e C++ e per di più acquisisce anche un po' di generalità (per quanto pericoloso sia il tipo void*).
    Di fronte a questi vantaggi, il fatto di usare [][] mi pare abbastanza trascurabile.
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Ma anche il mio codice ha tutti questi vantaggi visto che non uso i VLA ed è compatibile con il C++ . Alloco anche io un unico blocco di memoria In più ti permetto di usare la sintassi normale del C/C++ per le matrici.
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    
      getDimensions(&r, &c);
      int(*matrix)[c] = allocate(r, c); // questo è un VLA
    
    Guarda che tu stai usando i VLA e per di più forzi il tipo di matrice a essere di tipi int.
    E come detto, in C++ i VLA non sono ammessi.
    Estratto dell'ultimo working draft.
    11.3.4 Arrays
    In a declaration T D where D has the form
    D1 [ constant-expressionopt ] attribute-specifier-seqopt
    and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T”, then the type of the
    identifier of D is an array type; if the type of the identifier of D contains the auto type-specifier, the program
    is ill-formed. T is called the array element type; this type shall not be a reference type, cv void, a function
    type or an abstract class type. If the constant-expression (8.6) is present, it shall be a converted constant
    expression of type std::size_t and its value shall be greater than zero. The constant expression specifies
    the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N
    elements numbered 0 to N-1, and the type of the identifier of D is “derived-declarator-type-list array of N T”
    In altre parole, o la dimensione dell'array è una costante, oppure si deve allocare.
    g++ utilizza di default il flag -C11 o -C99, per quello da l'impressione che si possano usare i VLA in C++ quando in realtà non si può fare.
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    
      int(*matrix)[c] = allocate(r, c); // questo NON è un VLA
    
    Quello non è un VLA, ma un pointer-to-VLA ed in comune con i VLA ha solo il nome. È vero che è possibile utilizzarlo per memorizzare in esso l'indirizzo di un VLA, ma io lo utilizzo per uno scopo diverso. L'uso con i VLA è solo uno dei possibili utilizzi, quindi bloccare i VLA non significa bloccare in automatico l'esistenza di questo tipo di puntatore. Ad ogni modo vedrò per sicurezza di effettuare i giusti controlli, anzi grazie perché mi hai spinto a fare un ulteriore check della loro esistenza in compilatori più recenti che non prevedono/consentono l'uso dei VLA.

    Devo invece dissentire se dici che il tipo di matrix è forzato ad essere int e l'unica cosa che posso invitarti a fare è una ricerca su cosa sono i pointer-to-array.
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    Ho appena provato sia in C che in C++ togliendo il supporto alle features del C99 e compila/viene eseguito senza problemi. Non sentirti obbligato, ma se hai piacere di fare una prova anche tu puoi farmi sapere cosa succede. Alla fine lo scopo principale di questo thread era proprio un sano confronto
  • Re: C, matrici, allocazione dinamica ed eleganza del codice.

    cat prova.cpp
    #include <stdio.h>
    #include <stdlib.h>
    
    void getDimensions(unsigned *, unsigned *);
    int (*allocate(unsigned, unsigned))[];
    void doSomething(unsigned r, unsigned c, int(*matrix)[*]);
    
    
    int main()
    {
       unsigned r, c;
       
       getDimensions(&r, &c);
    
        int(*matrix)[c] = allocate(r, c);
       
       doSomething(r, c, matrix);
       
       free(matrix);
       
        return 0;
    }
    
    
    
    void getDimensions(unsigned *r, unsigned *c) {
       do {
        scanf("%u", r);
        scanf("%u", c);
       } while(*r == 0 || *c == 0); // unsigned -> negative values in input will be considered positive
       return;
    }
    
    
    int (*allocate(unsigned r, unsigned c))[]
    {
        return malloc(r * c * sizeof(int));
    }
    
    
    void doSomething(unsigned r, unsigned c, int(*matrix)[c]) {
       
       unsigned i = 0;
       unsigned j;
       
       for(; i < r; i++)
          for(j = 0; j < c; j++) 
             matrix[i][j] = j * i;
    
       for(i = 0; i < r; printf("%c", '\n'), i++)
          for(j = 0; j < c; j++)
             printf("%u ", matrix[i][j]);
       
       return;
    }
    
    Compilando con clang:
    
    clang -Weverything prova.cpp
    prova.cpp:6:54: warning: variable length arrays are a C99 feature
          [-Wvla-extension]
    void doSomething(unsigned r, unsigned c, int(*matrix)[*]);
                                                         ^
    prova.cpp:6:54: warning: variable length array used [-Wvla]
    prova.cpp:15:17: warning: variable length arrays are a C99 feature
          [-Wvla-extension]
        int(*matrix)[c] = allocate(r, c);
                    ^
    prova.cpp:15:17: warning: variable length array used [-Wvla]
    prova.cpp:15:10: error: cannot initialize a variable of type 'int (*)[c]' with
          an rvalue of type 'int (*)[]'
        int(*matrix)[c] = allocate(r, c);
             ^            ~~~~~~~~~~~~~~
    prova.cpp:37:12: error: cannot initialize return object of type 'int (*)[]' with
          an rvalue of type 'void *'
        return malloc(r * c * sizeof(int));
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~
    prova.cpp:41:54: warning: variable length arrays are a C99 feature
          [-Wvla-extension]
    void doSomething(unsigned r, unsigned c, int(*matrix)[c]) {
                                                         ^
    prova.cpp:41:54: warning: variable length array used [-Wvla]
    prova.cpp:48:27: warning: implicit conversion changes signedness: 'unsigned int'
          to 'int' [-Wsign-conversion]
             matrix[i][j] = j * i;
                          ~ ~~^~~
    prova.cpp:41:6: warning: no previous prototype for function 'doSomething'
          [-Wmissing-prototypes]
    void doSomething(unsigned r, unsigned c, int(*matrix)[c]) {
         ^
    8 warnings and 2 errors generated.
    
    Questo per quanto riguarda il C++, visto che se n'è parlato, mentre per il C:
    
    clang -Weverything prova.c -std=c90
    prova.c:6:54: warning: variable length arrays are a C99 feature
          [-Wvla-extension]
    void doSomething(unsigned r, unsigned c, int(*matrix)[*]);
                                                         ^
    prova.c:6:54: warning: variable length array used [-Wvla]
    prova.c:15:17: warning: variable length arrays are a C99 feature
          [-Wvla-extension]
        int(*matrix)[c] = allocate(r, c);
                    ^
    prova.c:15:17: warning: variable length array used [-Wvla]
    prova.c:15:10: warning: ISO C90 forbids mixing declarations and code
          [-Wdeclaration-after-statement]
        int(*matrix)[c] = allocate(r, c);
             ^
    prova.c:30:33: warning: // comments are not allowed in this language [-Wcomment]
       } while(*r == 0 || *c == 0); // unsigned -> negative values in input ...
                                    ^
    prova.c:41:54: warning: variable length arrays are a C99 feature
          [-Wvla-extension]
    void doSomething(unsigned r, unsigned c, int(*matrix)[c]) {
                                                         ^
    prova.c:41:54: warning: variable length array used [-Wvla]
    prova.c:48:27: warning: implicit conversion changes signedness: 'unsigned int'
          to 'int' [-Wsign-conversion]
             matrix[i][j] = j * i;
                          ~ ~~^~~
    prova.c:50:40: warning: possible misuse of comma operator here [-Wcomma]
       for(i = 0; i < r; printf("%c", '\n'), i++)
                                           ^
    prova.c:50:22: note: cast expression to void to silence warning
       for(i = 0; i < r; printf("%c", '\n'), i++)
                         ^~~~~~~~~~~~~~~~~~
                         (void)(           )
    10 warnings generated.
    
    
    clang -Weverything prova.c -std=c11
    prova.c:6:54: warning: variable length array used [-Wvla]
    void doSomething(unsigned r, unsigned c, int(*matrix)[*]);
                                                         ^
    prova.c:15:17: warning: variable length array used [-Wvla]
        int(*matrix)[c] = allocate(r, c);
                    ^
    prova.c:41:54: warning: variable length array used [-Wvla]
    void doSomething(unsigned r, unsigned c, int(*matrix)[c]) {
                                                         ^
    prova.c:48:27: warning: implicit conversion changes signedness: 'unsigned int'
          to 'int' [-Wsign-conversion]
             matrix[i][j] = j * i;
                          ~ ~~^~~
    4 warnings generated.
    
    Che venga compilato senza errori in C non mi stupisce e non l'ho mai messo in dubbio, il fatto è che non è detto che tutti i compilatori supportino tale idioma
Devi accedere o registrarti per scrivere nel forum
50 risposte