Oppure

Loading
28/06/17 8:27
AldoBaldo
Ciao a te che leggi.

Sto passando il tempo tentando di mettere insieme una micro-libreria (per uso personale, nel senso che non voglio farne chissà che) che tratti array dinamici di puntatori di qualsiasi tipo. Come al solito, più insisto più dubbi mi vengono, quindi tento di coinvolgerti.

L'idea parte dal presupposto, che credo corretto, che un puntatore è un puntatore, ovvero un indirizzo di memoria, ovvero un numero che ha una dimensione fissa determinata dal tipo di computer che si sta usando (16 bit ai "miei tempi", poi 32 bit, oggi si viaggia verso i 64 bit...). Per questo dovrei poter trattare un array di puntatori char* come un array di puntatori double* o di puntatori pinco_palla* senza che cambi gran che.

Dunque, definisco una struttura ARRAY_DINAMICO fatta così:

typedef struct {
    char **pEl; // puntatori correntemente contenuti nell'array
    size_t qEl; // quantita' degli elementi correntemente contenuti nell'array
    size_t cap; // capacita' massima corrente dell'array
} ARRAY_DINAMICO;


Nella mia fantasia, char **pEl dovrebbe corrispondere a char *pEl[], ovvero una serie di indirizzi (numeri) ciascuno dei quali ha la stessa dimensione in bit che avrebbe in double *pEl[] o pinco_palla *pEl[]. Ipotizzando che char* sia 32 bit, anche double* e' 32 bit e pinco_palla* è 32 bit, giusto?

Allora io alloco char **pEl così:

char **tmp = calloc( cap+AD_INC)*sizeof(*tmp) );


...dove cap è la capacità corrente dell'array e AD_INC è una costante che definisce un certo numero fisso di elementi da usare ogni volta che si espande o contrae l'array (in questo caso lo sto espandendo). In realtà uso realloc(), ma non credo faccia differenza. Le funzioni di "espansione" e "contrazione" complete sono:

int AD_IncrementaCapacita( ARRAY_DINAMICO *ad ) {
    if( NULL != ad ) {
        char **tmp = realloc( // segue nella riga sotto
            ad->pEl, (ad->cap+AD_INC)*sizeof(*tmp) );

        if( NULL != tmp ) {
            memset( tmp+ad->cap, 0, AD_INC*sizeof(*tmp) ); // annulla i nuovi puntatori
            ad->pEl = tmp;   // "consolida" il nuovo array, accettandolo in pEl
            ad->cap += AD_INC; // aggiorna la capacita' dell'array
            return AD_OK;
        } else return !AD_OK; // errore
    } else return !AD_OK; // errore
}

int AD_RiduciCapacita( ARRAY_DINAMICO *ad ) {
    if( NULL != ad ) {
        if( ad->cap-AD_INC >= ad->qEl ) {
            char **tmp = realloc( // segue nella riga sotto
                ad->pEl, (ad->cap>AD_INC?ad->cap-AD_INC:0)*sizeof(*tmp) );

            if( NULL != tmp ) {
                ad->pEl = tmp;   // "consolida" il nuovo array, accettandolo in pEl
                ad->cap -= AD_INC; // aggiorna la capacita' dell'array
                return AD_OK;
            } else return !AD_OK; // errore
        } else return !AD_OK; // errore
    } else return !AD_OK; // errore
}


Ora c'è il punto per me critico: quando implemento funzioni per "scrivere" e "leggere" i puntatori posso usare puntatori generici void* anche se la memoria è stata allocata come char**? Ad esempio con una funzioni tipo:

int AD_Inserisci( ARRAY_DINAMICO *ad, void *el, size_t pos ) {
    if( NULL != ad && NULL != el && pos <= ad->qEl ) {
        if( ad->qEl+1 > ad->cap )
            if( !AD_IncrementaCapacita(ad) )
                return !AD_OK; // errore

        memmove( ad->pEl+pos+1, ad->pEl+pos, (ad->qEl-pos)*sizeof(*ad->pEl) );
        ad->pEl[pos] = el;
        ++ad->qEl;
        return AD_OK; // ok
    } else return !AD_OK; // errore
}

void *AD_Leggi( const ARRAY_DINAMICO *ad, size_t pos ) {
    if( NULL != ad && pos < ad->qEl )
        return ad->pEl[pos];
    else return NULL; // errore
}


...rischio qualche cedimento della struttura dell'Universo se procedo a questo modo?

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

typedef struct pinco_palla {
    char una_stringa[32];
    float un_numero;
} PINCO_PALLA;

int main() {
    PINCO_PALLA pp, *ppPtr;
    ARRAY_DINAMICO ad;
    
    strcpy( pp.una_stringa, "pippo" );
    pp.un_numero = 10.2f; // 10.2 e' senz'altro un bel numero
    
    AD_Azzera( &ad );
    
    if( AD_OK == AD_Inserisci(&ad,&pp,0) ) {
        ppPtr = (PINCO_PALLA*) AD_Leggi( &ad, 0 );
        printf( "%s, %f\n\n", ppPtr->una_stringa, ppPtr->un_numero );
    } else printf( "Errore!!!\n\n" );
    
    getchar();
    return 0;
}


Il compilatore che sto usanto (gcc in mingw) non batte ciglio e compila senza neanche un warning, però se non ho capito male a volte ci sono cosette che esulano dallo standard e che determinano comportamenti indefiniti che alcuni compilatori segnalano e altri no.

Lo so che son domande un po' così, però ogni tanto entro in questi odiosi loop di dubbi confusionari e mi torna davvero comodo un parere "a freddo". Mi aiuti, senza voli pindarici troppo alti sopra la mia testa?
Ultima modifica effettuata da AldoBaldo 28/06/17 8:41
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.
28/06/17 8:55
Mikelius
Postato originariamente da AldoBaldo:

char **tmp = calloc( cap+AD_INC)*sizeof(*tmp) );


Qui mi pare che manchi una parentesi, ma sarà errore di battitura XD



Comunque, è come dici, in teoria la dimensione di un puntatore è fissata dal sistema in quanto la dimensione di un indirizzo non dovrebbe essere variabile. Però non so i problemi che si creano a creare l' ARRAY void* e successivamente usare nel main un cast() ad esempio.


Ultima modifica effettuata da Mikelius 28/06/17 8:58
aaa
28/06/17 9:19
AldoBaldo
Hai ragione: è un errore di battitura (infatti la versione che ho nei miei codici è diversa e usa realloc(); copiando, incollando e modificando mi son partite un paio di inesattezze).

Doveva essere:

char **tmp = malloc( (cap+AD_INC)*sizeof(*tmp) );


Scrivi: "non so i problemi che si creano a creare l' ARRAY void* e successivamente usare nel main un cast", che è proprio quel che sto cercando di capire! Mi sembra che non debbano essercene, ma sarebbe utile se chi se ne intende più di me mi desse conferma o smentita certa.

Ovvio che quando si fanno cast occorre sempre stare attenti, ma quella è una caratteristica comune del C, tanto che le funzioni di allocazione usano tutte void*. Una volta chiarito il mio dubbio, senz'altro mi metterò col cesello in mano a rifinire tutti i dettagli necessari per evitare errori dovuti ai cast.
Ultima modifica effettuata da AldoBaldo 28/06/17 9:21
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.
28/06/17 12:17
nessuno
In questa linea

ad->pEl[pos] = el;

devi dirlo che è un puntatore a char

ad->pEl[pos] = (char *)el;
Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
---
Il grande studioso italiano Bruno de Finetti ( uno dei padri fondatori del moderno Calcolo delle probabilità ) chiamava il gioco del Lotto Tassa sulla stupidità.
28/06/17 21:08
lumo
Un giorno ti convincerò ad usare incrementi moltiplicativi invece che addizionali quando estendi l'array, purtroppo sono un po' impegnato in questi giorni.
aaa
28/06/17 21:20
TheDarkJuster
Una precisazione: la dimensione di un puntatore dipende da tre fattori: CPU, modo di operazione (modalità reale o protetta) e se il so ha attivato la paginazione. Credo che un indirizzi di memoria virtuale in alcuni sistemi sia di 48bit, correggettermi se sbaglio.
aaa
29/06/17 5:32
AldoBaldo
Grazie per le risposte, anche se i miei dubbi non si dissipano ancora. Vi rispondo uno per uno.

==============================

Nessuno, perché quel cast è necessario? A me sembra come quando si fa una cosa tipo...

int *intPtr = malloc( sizeof(*intPtr) );


In quel caso non si usa il cast, no? Si prende il puntatore void* e lo si usa da quel momento in poi in intPtr come se fosse un puntatore int* qualsiasi. Mi son perso qualcosa (d'altro)?

==============================

Lumo, ricordo la discussione in merito alla tipologia di incremento. Le mie "resistenze" risalgono a quando facevo cose per un computer con 1 MB di RAM in tutto e una gestione della memoria non "protetta", dove ogni programma era "confinato" entro uno spazio preallocato dal sistema e non estendibile che per forza di cose doveva essere alquanto ristretto. Ci sono imprinting dai quali è difficile liberarsi anche quando le condizioni di contorno cambiano drasticamente. E' questo che rende vecchie le persone di una certa età.

==============================

TheDarkJuster, quel che osservi (e di cui non so nulla) che ricadute ha sul caso specifico? E' una cosa che influisce sulla portabilità del codice (che per quel che ho in mente sarebbe poco importante) o può creare problemi nel momento in cui uno stesso programma già compilato viene fatto "girare" in una modalità piuttosto che un'altra anche sullo stesso computer?
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.
29/06/17 7:49
nessuno
In C++ è obbligatorio. Non in C.

Ma il codice è bene che sia "portabile" anche verso compilatori C++.
Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
---
Il grande studioso italiano Bruno de Finetti ( uno dei padri fondatori del moderno Calcolo delle probabilità ) chiamava il gioco del Lotto Tassa sulla stupidità.