PDA

View Full Version : C curiosità sui puntatori


misterx
27-04-2020, 09:25
se uso una variabile globale del tipo:

char *pippo;

e poi la uso in una funzione senza parametri nella quale ne incremento l'indirizzo, quando ritorno al chiamante mi ritrovo con la variabile pippo che punta ad un nuovo indirizzo.

Se invece costruisco una funzione dove come parametro passo proprio quel puntatore e lo incremento, al ritorno pippo è come se non si fosse mossa: è normale questo funzionamento?

Riassumendo: (attenzione, codice non testato e magari zeppo di errori)


char *pippo;

main()
{
pluto();
}

void pluto()
{
pippo++;
}
// ------------------------------------------
char *pippo;
int pluto(char *pippo);

main()
{
pluto(pippo);
}

int pluto(char *pippo)
{
return pippo++;
}

misterx
27-04-2020, 12:57
risolto ho preso un abbaglio

Lampo89
27-04-2020, 21:22
Ciao,

se uso una variabile globale del tipo:

char *pippo;

e poi la uso in una funzione senza parametri nella quale ne incremento l'indirizzo, quando ritorno al chiamante mi ritrovo con la variabile pippo che punta ad un nuovo indirizzo.

Se invece costruisco una funzione dove come parametro passo proprio quel puntatore e lo incremento, al ritorno pippo è come se non si fosse mossa: è normale questo funzionamento?

Riassumendo: (attenzione, codice non testato e magari zeppo di errori)


char *pippo;

main()
{
pluto();
}

void pluto()
{
pippo++;
}
// ------------------------------------------
char *pippo;
int pluto(char *pippo);

main()
{
pluto(pippo);
}

int pluto(char *pippo)
{
return pippo++;
}


sì è corretto come da specifiche. Nel primo caso hai appunto definito una variabile globale che è visibile anche in tutte le translation units grazie alla magica magia del linker. Nel secondo caso, vale la semantica pass-by-value solita: alla chiamata di una funzione viene creato un nuovo stack frame in cui sono copiati il valore dei parametri passati.
Morale della favola: meglio evitare, se possibile, variabili globali ...

misterx
28-04-2020, 07:29
però ho idea di non avere alternative all'uso di una variabile globale per via della storia del passaggio per valore e non per riferimento.

misterx
28-04-2020, 11:48
approfitto di questo mia 3d per chiedervi:

se voglio sapere se l'MSB è settato, mi basta nel caso di un byte verificare che il suo valore sia < 128.
Tutti quelli maggiori o uguali hanno l'MSB settato a 1, ma perchè molti invece usano questa forma?

byte miobyte = 129;
byte MSB = (miobyte >> 7) & 1;

wingman87
28-04-2020, 18:21
però ho idea di non avere alternative all'uso di una variabile globale per via della storia del passaggio per valore e non per riferimento.
Invece di passare il valore della variabile, puoi passare l'indirizzo della variabile
Esempio:

main()
{
char *pippo;
//... pippo viene inizializzato in qualche modo
pluto(&pippo);
//giunti qui pippo è stato incrementato di 1
}

int pluto(char **pippo)
{
(*pippo)++;
}
Ho messo le parentesi in (*pippo)++; perché non ricordo la precedenza degli operatori, nel dubbio con le parentesi non ci sono equivoci.
approfitto di questo mia 3d per chiedervi:

se voglio sapere se l'MSB è settato, mi basta nel caso di un byte verificare che il suo valore sia < 128.
Tutti quelli maggiori o uguali hanno l'MSB settato a 1, ma perchè molti invece usano questa forma?

byte miobyte = 129;
byte MSB = (miobyte >> 7) & 1;
Forse perché questo è un modo generico di ottenere il valore del bit n+1 all'interno del byte (nel tuo esempio n=7). Inoltre dovrebbe funzionare anche per tipi di variabili signed e con più byte, tipo int (scusa se uso il condizionale ma vado a memoria).

misterx
28-04-2020, 18:50
Invece di passare il valore della variabile, puoi passare l'indirizzo della variabile
Esempio:

main()
{
char *pippo;
//... pippo viene inizializzato in qualche modo
pluto(&pippo);
//giunti qui pippo è stato incrementato di 1
}

int pluto(char **pippo)
{
(*pippo)++;
}
Ho messo le parentesi in (*pippo)++; perché non ricordo la precedenza degli operatori, nel dubbio con le parentesi non ci sono equivoci.

Forse perché questo è un modo generico di ottenere il valore del bit n+1 all'interno del byte (nel tuo esempio n=7). Inoltre dovrebbe funzionare anche per tipi di variabili signed e con più byte, tipo int (scusa se uso il condizionale ma vado a memoria).


si, è proprio come nel tuo esempio.
Così posso evitare variabili globali che potrebbero creare qualce problema di difficile soluzione.
Non aveva mai usato funzioni col doppio asterisco, c'è smpre una prima volta; grazie 1000

misterx
29-04-2020, 08:36
vi risulta una cosa del genere?


char *a;
a = (char *) malloc(sizeof(char) * 10);
a+=5;
free(a); // errore, non viene liberata tutta la memoria

frncr
30-04-2020, 03:28
Il parametro passato a free() deve corrispondere a un indirizzo precedentemente restituito da una malloc(), altrimenti il comportamento della funzione è indefinito, perciò quel codice è effettivamente errato.

misterx
30-04-2020, 10:36
grazie per la conferma

Volutomitra
01-05-2020, 12:51
approfitto di questo mia 3d per chiedervi:

se voglio sapere se l'MSB è settato, mi basta nel caso di un byte verificare che il suo valore sia < 128.
Tutti quelli maggiori o uguali hanno l'MSB settato a 1, ma perchè molti invece usano questa forma?

byte miobyte = 129;
byte MSB = (miobyte >> 7) & 1;

Io farei, e di solito faccio, così:

(miobyte & 0x80)

Ma ragiono per microcontrollori, dove sprecare operazioni inutili è un delitto :D

WarDuck
03-05-2020, 09:09
Io farei, e di solito faccio, così:

(miobyte & 0x80)

Ma ragiono per microcontrollori, dove sprecare operazioni inutili è un delitto :D

Sono sempre per la soluzione che è più facile da leggere, come la tua ;).

In ogni caso lì dove entrano in gioco costanti, come nel caso accennato da misterx, è molto probabile che il compilatore ottimizzi già da se quelle operazioni, se le ottimizzazioni sono abilitate (come dovrebbe sempre essere :D).

Bisognerebbe guardare l'assembly :sofico:

Una precisazione però, bisogna fare attenzione al tipo di dato, il tipo "byte" in C non esiste, parliamo di char o unsigned char. Nel caso di numeri con segno, i bit più significativi possono essere 1 nel caso di numeri negativi in rappresentazione a complemento a 2.

In generale per memorizzare i flag infatti è consigliabile usare numeri senza segno.

Volutomitra
04-05-2020, 14:28
Sono sempre per la soluzione che è più facile da leggere, come la tua ;).

In ogni caso lì dove entrano in gioco costanti, come nel caso accennato da misterx, è molto probabile che il compilatore ottimizzi già da se quelle operazioni, se le ottimizzazioni sono abilitate (come dovrebbe sempre essere :D).

Bisognerebbe guardare l'assembly :sofico:


Siccome sono un rompiballe :fagiano: sono andato a vedere :doh:


unsigned char test1(unsigned char value) {
return (value & 0x80);
}

unsigned char test2(unsigned char value) {
return ((value >> 7) & 1);
}


compilando con gcc x86-64 9.3, diventa:


test1:
push rbp
mov rbp, rsp
mov eax, edi
mov BYTE PTR [rbp-4], al
movzx eax, BYTE PTR [rbp-4]
and eax, -128
pop rbp
ret
test2:
push rbp
mov rbp, rsp
mov eax, edi
mov BYTE PTR [rbp-4], al
movzx eax, BYTE PTR [rbp-4]
shr al, 7
pop rbp
ret


Ho evidenziato le istruzioni corrispondenti ai rispettivi return. Il verdetto è che WarDuck ha ragione, il compilatore ottimizza. ;)

Resta il fatto che ovviamente anche per me la prima versione è più leggibile :D

Una precisazione però, bisogna fare attenzione al tipo di dato, il tipo "byte" in C non esiste, parliamo di char o unsigned char. Nel caso di numeri con segno, i bit più significativi possono essere 1 nel caso di numeri negativi in rappresentazione a complemento a 2.

In generale per memorizzare i flag infatti è consigliabile usare numeri senza segno.

In questo caso avevo ovviamente dato per scontato che il tipo byte fosse unsigned char...

WarDuck
05-05-2020, 19:16
Siccome sono un rompiballe :fagiano: sono andato a vedere :doh:


unsigned char test1(unsigned char value) {
return (value & 0x80);
}

unsigned char test2(unsigned char value) {
return ((value >> 7) & 1);
}


compilando con gcc x86-64 9.3, diventa:


test1:
push rbp
mov rbp, rsp
mov eax, edi
mov BYTE PTR [rbp-4], al
movzx eax, BYTE PTR [rbp-4]
and eax, -128
pop rbp
ret
test2:
push rbp
mov rbp, rsp
mov eax, edi
mov BYTE PTR [rbp-4], al
movzx eax, BYTE PTR [rbp-4]
shr al, 7
pop rbp
ret


Ho evidenziato le istruzioni corrispondenti ai rispettivi return. Il verdetto è che WarDuck ha ragione, il compilatore ottimizza. ;)

Resta il fatto che ovviamente anche per me la prima versione è più leggibile :D


C'è da dire che i due codici non sono perfettamente equivalenti, la prima funzione ritorna 0 oppure 0x80, mentre la seconda 0 oppure 1.

Se uno vuole sapere solo se il bit è settato (quindi è diverso da 0) allora va bene il primo metodo.

Tuttavia bisogna stare attenti perché se si fa il confronto chiedendosi se quel test è uguale a 1 ad esempio, chiaramente si sbaglia.

La cosa interessante è che usando il trucco di negare doppiamente il valore nella prima funzione (portando i valori tra 0 e 1):


unsigned char test1(unsigned char value)
{
return !!(value & 0x80u);
}

unsigned char test2(unsigned char value)
{
return ((value >> 7u) & 1u);
}


Si ottiene lo stesso assembly (GCC 9.2.x):

0000000000000000 <test1>:
0: 89 f8 mov %edi,%eax
2: c0 e8 07 shr $0x7,%al
5: c3 retq
6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
d: 00 00 00

0000000000000010 <test2>:
10: 89 f8 mov %edi,%eax
12: c0 e8 07 shr $0x7,%al
15: c3 retq


È interessante anche notare che l'AND 1 viene eliminato dalla seconda funzione. Questo succede perché entrambi i tipi, del valore e di ritorno sono "unsigned char" e si fa uno shift di 7 quindi il compilatore sà per certo che nell'output tutti i bit eccetto il primo sono 0, rendendo non necessario l'AND.

In tutti gli altri casi, l'AND invece va fatto altrimenti il codice non sarebbe corretto :D. Se usate unsigned int ad esempio, l'AND compare:


0000000000000010 <test2>:
10: 89 f8 mov %edi,%eax
12: c1 e8 07 shr $0x7,%eax
15: 83 e0 01 and $0x1,%eax
18: c3 retq


Le bellezze di GCC :sofico:

misterx
05-05-2020, 19:27
siete andati avanti, grandi :D

Io sto usando quella verifica in un


while(variabile < 0x80)
{ ...... }


ed ho notato in definitiva svolge lo stesso compito