Puntatori ... deep inside

Buongiorno a tutti,
Sicuramente qualcuno alla fine della lettura di questo post si domanderà del perchè perdo tanto tempo ad inseguire spiegazioni e concetti a prima vista inutili.
La risposta è semplice,'funziona così e basta' non fà al caso mio :grin:

Argomento, come da titolo PUNTATORI

dato il codice in oggetto :

 struct a_Struct
  {
    byte a1;
    byte a2;
    byte a3;
  };


  byte buffer[10] = {0,1,2,3,4,5,6,7,8,9};
  a_Struct * prtTo_a = (a_Struct *)&buffer[5];

Suppongo che prtTo_a utilizzi 2 byte , ed il tipo specificato (a_Struct * ) identifichi la struttura dei dati.
Quello che non riesco a capire esattamente è :

  1. quanto spazio realmente viene allocato in memoria per l'operazione in oggetto? (io suppongo siano 10 byte per buffer[] + 2 byte per ptrTo_a totale 12byte, è corretto o va considerato altro?)

  2. se ptrTo_a è formato da soli 2 byte (indirizzo del puntatore) chi è che si smazza per fare in modo che nel programma tramite quella dichiarazione risulti del tipo a_Struct ? (l'unica cosa sensata che ho pensato è che il tutto finisce una volta compilato ed è il compilatore stesso che si occupa di puntare esattamente ai singoli valori della struttura quando richiesti)

Grazie a tutti quanti vorranno partecipare!!!

Si esatto è il compilatore che fa tutto il necessario, tanto per esso un indirizzo contenuto in un array o un elemento della struttura sempre in un indirizzo di memoria viene salvato.

Però non mi convince quel codice, sembra un cast esplicito del 6° elemento di un array di byte, se quel codice funziona è anche possibile compilare una struttura in modo parziale, cioè solo un campo, però perchè questo avvenga con successo sia la struttura e l'array dovrebbero avere la stessa dimensione, altrimenti il resto dell'array dal 7 elemento al 10 dove finisce, visto che non è possibile accedervi tramite struttura.

Sono confuso, dovrei fare delle prove per vedere cosa succede.
Curiosità dove hai trovato il codice postato?

Ciao.

L'ho scritto io , di polso, e l'ho anche provato.
In realtà non è come dici tu, il risultato ottenuto è esattamente quello che avrei sperato, ovvero che viene valorizzata la struttura con i byte dell'array a partire dal 6sto elemento.
Che fine fanno gli altri valori? Semplice, in realtà rimangono esattamente dove sono sempre stati, casomai il problema di pone se non ci sono abbastanza elementi nell'array da assegnare alla struttura, ma non credo comunque che ci siano errori, ma semplicemente che venga 'riempita' con valori 'a muzzo' (sempre che non si vada fuori dall'area di memoria disponibile)

Seccede esattamente questo :

l'indirizzo iniziale di buffer è ad esempio 0x00
l'indirizzo del puntatore a struttura viene valorizzato con 0x05
E' come avere una finestra nell'array.

Io l'ho interpretata così:

Il puntatore alla struttura a_Struct, di nome prtTo_a , ha come indirizzo iniziale l'indirizzo del 6sto elemento dell'array buffer.

difatti interrogando i valori della struttura a1, a2 e a3, ottengo rispettivamente 5,6,7 .

Giustifico la necessità di dover specificare il cast (a_Struct *) con il fatto che il compilatore deve comunque sapere ' cosa c'è ' nel puntatore di destinazione.

A volte ritengo che alcune manovre siano veramente insitamente molto criptiche :expressionless:

Ok, noi ci siamo capiti ora per gli altri.

In realtà non è come dici tu, il risultato ottenuto è esattamente quello che avrei sperato, ovvero che viene valorizzata la struttura con i byte dell'array a partire dal 6sto elemento.

Esatto, ma non si tratta di una valorizzazione in quanto non abbiamo dichiarato alcuna variabile di tipo struttura e quindi non abbiamo impegnato memoria. Cioè che facciamo è di accedere agli elementi dell'array 6,7,8 attraverso un puntatore a struttura definita in a_Struct. Pertanto i dati oltre l'elemento 8 dell'array rimangono accessibili solo attraverso l'indice delll'array stesso.

Giustifico la necessità di dover specificare il cast (a_Struct *) con il fatto che il compilatore deve comunque sapere ' cosa c'è ' nel puntatore di destinazione.

A volte ritengo che alcune manovre siano veramente insitamente molto criptiche smiley-neutral

Si la necessità che ha il compilatore potrebbe essere legata al fatto che in questo modo esplicitiamo il cast evitando possibili incomprensioni che potrebbero venire fuori durante l'esecuzione. Si il C è molto criptico, ma il massimo di cripticità il lo provato difronte alle espressioni regolari, dove un solo simbolo assume diversi significati in base alla posizione e al simbolo vicino.

Da notare l'operatore di reference & usato in quel codice qui "&buffer[5]" questo è necessario perchè l'accesso tramite indice dereferenzia (*) l'array buffer che usato da solo è un indirizzo di memoria, quindi la dereferenziazione ritorna il valore contenuto nella 6 cella di memoria (12esima se si trattasse di int), ma a noi invece interessa l'indirizzo di memoria per assegnarlo ad un puntatore ad indirizzo di memoria di tipo struttura.

Peccato che questa cosa non si può fare con le strutture bitfield, perchè sarebbe comodo accedere ad ogni singolo bit di un byte, int attraverso un campo di una struttura. Nei microcontroller della serie xmega è possibile accedere ai registri come si trattasse di una struttura.

Ciao.

MauroTec:
Esatto, ma non si tratta di una valorizzazione in quanto non abbiamo dichiarato alcuna variabile di tipo struttura e quindi non abbiamo impegnato memoria. Cioè che facciamo è di accedere agli elementi dell'array 6,7,8 attraverso un puntatore a struttura definita in a_Struct. Pertanto i dati oltre l'elemento 8 dell'array rimangono accessibili solo attraverso l'indice delll'array stesso.

Non sono del tutto convinto di quanto hai detto sopra.
Due sono le principali cause che mi hanno portato a smentirti:

  1. i valori della struttura sono copie dei valori del buffer, in quanto se li modifico, poi cambiano nella struttura, ma non nel buffer!
  2. ha anche senso quanto detto sopra visto che nella struttura i 3 byte sono definiti come tali e non come puntatori.

Ora le cose si complicano, perchè stò facendo altri giochini che danno risultati tutt'altro che attesi!

 struct a_Struct
  {
    byte * a1[3];
  };

    uint16_t buffer[7]= {0x0201,0x0403,0x0605,0x0807,0x0a09,0x0c0b,0x0e0d};
    
    a_Struct a;

    a.a1[0] = (byte *)&buffer[3];

    // prova un pò ad indovinare cosa contiene a1[0] , a1[1] ed a1[2] 

    *a.a1[0] = 0xff;
    *a.a1[1] = 0xee;
    *a.a1[2] = 0xdd;

     // prova ora ad indovinare cosa contiene buffer[3] (entrambi i byte) e buffer[4] lsb

misteri...

Non sono del tutto convinto di quanto hai detto sopra.
Due sono le principali cause che mi hanno portato a smentirti:

  1. i valori della struttura sono copie dei valori del buffer, in quanto se li modifico, poi cambiano nella struttura, ma non nel buffer!
  2. ha anche senso quanto detto sopra visto che nella struttura i 3 byte sono definiti come tali e non come puntatori.

Strano quello in comportamento, se cambi il valore salvato nell'elemento 6 dell'array da 0xff a 0xfe, ptrTo_a.a1 dovrebbe contenere 0xfe.

L'ultimo esempio non mmi pare per niente strano.

&buffer[3]; estrae l'indirizzo di memoria della cella in cui si trova memorizzato il 4 elemento dell'array, dato che è grande 16 bit.

(byte *) cast esplicito di un dato grande 16 bit ad uno di 8 bit, risultato gli 8 bit più significativi vengono persi e rimango i meno significativi.

Poi ciò che rimane lo memorizzi in un array di byte.

nota che buffer contiene elementi da 16 bit, sottraendo 0xff - 0xee = 17, 0xee - 0xdd = 17. Prova invece a stampare &buffer[3], deve essere un intero senza segno, dove gli 8 bit meno significativi valgono 0xff.

PS: continua che mi piace. :stuck_out_tongue:

ciao.

MauroTec:
Strano quello in comportamento, se cambi il valore salvato nell'elemento 6 dell'array da 0xff a 0xfe, ptrTo_a.a1 dovrebbe contenere 0xfe.

Come dici tu non ho provato, ho provato viceversa e non accadeva, e di fatto ho pensato che il problema era relativo al fatto che i byte dentro la struttura non erano puntatori , ma valori.
Cambiando però il valore ai dati della struttura il buffer rimaneva invariato.

MauroTec:
L'ultimo esempio non mmi pare per niente strano.
&buffer[3]; estrae l'indirizzo di memoria della cella in cui si trova memorizzato il 4 elemento dell'array, dato che è grande 16 bit.
(byte *) cast esplicito di un dato grande 16 bit ad uno di 8 bit, risultato gli 8 bit più significativi vengono persi e rimango i meno significativi.

In realtà non si perde niente, solo che essendo il puntatore ad 8 bit invece che 16 va castato, di fatto il puntatore ad 8 bit punta al byte meno significativo di quell' uint16_t, ma i dati nel buffer non cambiano.
Mi aspettavo però che il secondo ed il terzo elemento di byte * a1[3] contenuto nella struttura contenessero rispettivamente l'indirizzo del byte più significatico di buffer[3] e il meno significativo di buffer[4], cosa che invece non accade.
Ho controllato gli indirizzi e sono perfettamente seguenti uno dall'altro 2 byte(dimensione del puntatore immagino) e contengono valori del tutto diversi dal nostro contesto .

Poi mi sono illuminato d'immenso e ho creato un semplice puntatore a byte che scorro come se fosse un array (che ovviamente non è) e riesco ad accedere sia in lettura che in scrittura agli elementi che voglio:

    byte * a ;
    
    a = (byte *)&buffer[3];

    valore puntato da a[0] -> 7 ;
    valore puntato da a[1] -> 8 ;
    valore puntato da a[2] -> 9 ;

    a[1] = 0xFF;
    a[1] = 0xee;
    a[2] = 0xdd;

    // i valori vengono correttamente variati anche nel buffer

Niente che non sapevo già, non ho scoperto niente.
Però mi sà tanto che quello che vorrei realizzare io non è del tutto fattibile.
Vorrei avere una struttura , contenente insiemi di byte che altro non sono che porzioni del contenuto del buffer, e per fare ciò avevo avuto la presunzione di pensare che bastasse far puntare il primo elemento della struttura al primo byte del buffer che conteneva i dati per quella struttura.

MauroTec:
PS: continua che mi piace. :stuck_out_tongue:

Oddio, detta così non suona molto bene ! :fearful:

Nerdgasm :stuck_out_tongue:

Stimolato dal desio, e dall'internazional intervento di tuxduino ho prodotto un'altro interessante frammento di codice:

 struct a_Struct
  {
    byte  a1[2];
    byte  a2[2];
    byte  a3[2];
  };

    uint16_t buffer[7]= {0x0201,0x0403,0x0605,0x0807,0x0a09,0x0c0b,0x0e0d};

    a_Struct * g = (a_Struct*)&buffer[2];
    
    Serial.println(g->a1[0],HEX); // output generato : 5
    Serial.println(g->a1[1],HEX); // output generato : 6
    Serial.println(g->a2[0],HEX); // output generato : 7
    Serial.println(g->a2[1],HEX); // output generato : 8
    Serial.println(g->a3[0],HEX); // output generato : 9
    Serial.println(g->a3[1],HEX); // output generato : A
    
    g->a1[0]= 0xAF;
    g->a1[1]= 0xFA;
    g->a2[0]= 0xBE;
    g->a2[1]= 0xEB;
    g->a3[0]= 0xCD;
    g->a3[1]= 0xDC;
    
    uint8_t* c = (uint8_t*)&buffer;
    
    for(int i = 0 ; i < sizeof(buffer);i++)
    { 
      Serial.print(*c,HEX);
      Serial.print(" - ");
      c++;
    }
    Serial.println();

// output generato :  1 - 2 - 3 - 4 - AF - FA - BE - EB - CD - DC - B - C - D - E -

Quindi alla fine sono riuscito a creare una sorta di quello che volevo, una struttura contenente degli array di byte che riesco ad inizializzare con l'indirizzo iniziale del buffer.

Ora mi piacerebbe capire con esattezza quanta memoria viene allocata per il puntatore alla struttura, in teoria solo 2 byte, giusto?

Serial.println(sizeof(struct a_Struct *));

mi pare

@niki77

Se i valori dell'array cambiano e si riflettono alla struttura e viceversa stai occupando lo spazio di un puntatore di 16 bit. Mentre se non cambiano come penso complessivamente stai usando ptr 2 byte + a1,a2,a3=6 byte totale 8 byte. Mentre se i campi della struttura fossero puntatori a interi sensa segno occupi sempre 8 byte ma i valori si riflettono. Un puntatore viene salvato in un suo indirizzo di memoria e punta ad un'altro quindi complessivamente 8 byte per la struttura e 14 byte per l'array.

Ciao.

Ok, si, sto usando un puntatore a 16 bit, occupa 2 byte , va benissimo così.

Ora visto che siamo in tema adiacente vorrei affrontare un altro mio dubbio amletico .(perdonatevi eventuali domande stupide ma negli ultimi 10 anni ho usato quasi esclusivamente linguaggi interpretati e non ho avuto modo di forgiare competenze un pò più advanced dei linguaggi compilati)

int A = 128; // Creo una variabile di tipo int (2 byte) 

int * prtA = &a; // questo è un puntatore ad A , ovvero una 'variabile' di tipo puntatore che contiene l'indirizzo di memoria della variabile A
//occupa in memoria 2 byte(non perchè contiene un intero ma perchè un puntatore occupa 2 byte,perlomeno su questa architettura)

Veniamo al dunque, per semplicità farò l'esempio col tipo INT (2 byte).

void changeMyVal(int myval)
{
myval ++;
}
// che richiamo in questa maniera
int myInt = 128;
changeMyVal(myInt); 
//giustamente qui il valore di myInt è rimasto 128, perchè nella chiamata è stata creata una variabile myVal che contiene lo stesso valore numerico //(128)della variabile passata dalla chiamata stessa.

Se invece :

void changeMyVal(int * myval)
{
*myval ++;
}

// che richiamo in questa maniera

int myInt = 128;
changeMyVal(&myInt); 
//il valore in di myInt quì è stato correttamente portato a 129 perchè dentro la funzione è stato incrementato.Tuttavia, se non ho capito male, anche in questo caso la chiamata ha creato una variabile di tipo puntatore ad intero(quindi allocando 2 ulteriori byte) contenente la locazione di memoria del mio myInt .

Ora , tralasciando tutto il discorso di scope e visibilità variabili, allocazioni e deallocazioni, la domanda è questa:
E' possibile fare in modo che chiamando una procedura e/o funzione si possa passare per riferimento DIRETTAMENTE una variabile definita in un altro contesto ?

praticamente

int myInt = 128;
int * prtA = &myInt ; // qui ho già creato un puntatore a myInt 

// vorrei chiamare la procedura
changeMyVal(prtA); 
// passando come parametro QUEL ESATTO PUNTATORE per riferimento , e non creandone un altra copia.

oppure anche

int myInt = 128;

// vorrei chiamare la procedura
changeMyVal(myInt); 
// passando come parametro QUELLA VARIABILE ESATTA per riferimento , e non creandone un altra copia.

Sperando di essere stato comprensibile, sto sbarellando o si può fare?
Grazie

Si è possibile e lo stai già facendo nel primo e ultimo esempio. I puntatori passati come argomento di funzione sono più efficienti perchè automaticamente non viene creata una copia locale, cosa che accade solo passando la variabile per valore.

Usare & per referenziare è di uso comune e non occorre creare un puntatore prima della chiamata, in questo modo si risparmi tempo per allocare il puntatore.

I puntatori sono molto convenienti quando fanno riferimento ad una struttura oggetto di grandi dimensioni, più grande è più conviene. Usare un puntatore a byte che comunque è grande 16 bit non è conveniente, ma se è richiesto per la funzionalità pasienza.

Ciao.

Intanto grazie per il supporto!

MauroTec:
I puntatori sono molto convenienti quando fanno riferimento ad una struttura oggetto di grandi dimensioni, più grande è più conviene. Usare un puntatore a byte che comunque è grande 16 bit non è conveniente, ma se è richiesto per la funzionalità pasienza.

Mi permetto di aggiungere che, a volte, non è solo questione di convenienza, ma anche perchè non si può fare diversamente.
Ad esempio se da una funzione ho necessità di ritornare una serie di risultati superiori a 1.
In questo caso la funzione si comporta da 'FILLER' di oggetti/variabili.

oppure come nel mio caso, quando hai una serie di valori alla rinfusa dentro un array e vuoi modificarli più praticamente tramite una struttura. :grin:

Per impacchettare dati in un array ed estrarli singolarmente si possono usare le define o enum al fine di creare delle costanti numeriche da usare come indice di array, oppure ancora si possono creare funzioni/macro che ritornano il dato e prendono il puntatore ad array è la costante, se non si vuole usare l'operatore [] per estrarre il dato. Oppure come hai fatto tu, con le strutture ma occupando un po di spazio in più, comunque fintanto che le dichiarazioni sono dentro i blocchi {} cessano di esistere all'esterno liberando memoria.

Sta diventando una discussione a due.

Ciao.