PDA

Visualizza versione completa : I puntatori del C


Flavio58
24-04-2007, 11.00.55
Quando si vuole gestire l'ottica dei puntatori si deve avere sempre a mente che in effetti i dati sono memorizzati in certe locazioni di memoria e che a seconda dei tipi questi tengono un certo numero di bytes.
Un puntatore è come una variabile normale ma solo che il valore che contiene è un indirizzo di un altra locazione di memoria dove viene mantenuto un valore di un ceryo tipo.
In genere quando si cedono cose del tipo :

char *c;
int *c;
struct abc *c;

uno si chiede : ma se un puntatore è uno spazio sufficente a contenere un indirizzo cosa me ne frega di scrivere int *, char * ecc. visto che alla fine gli indirizzi sono sempre lunghi uguale ?
Perchè il tipo dice al sistema che il dato a quell'indirizzo è di un certo tipo e quindi occupa N bytes.
La tipologia influsice sull'aritmetica dei punatori.

In pratica se io avessi :

int *n = (int *) 0x00000C000;

e se poi incrementassi l'indirizzo cosi :

++n;

Di quanti bytes verrebbe incrementato n ?
Semplicemente di :

n = n + (1 * sizeof(int));

ovvero, nel caso di int = 4 bytes, di 4 bytes appunto !
L'aritmetica dei punatori è potente.
Prendete ad esempio il problema di copiare 10 bytes di un array in un altro array.
Norlamente tutti farebbero :

int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
int b[10];

int x;

for(x=0;x!=10;i++)
b[x] = a[x];


Con i punatori potrei fare un codice, che se andate a vederlo in assembler tradotto dal compilatore, molto piu' compatto :

int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
int b[10];
struct x { int a[10]; }; // Solo definizione -- NON DICHIRAZIONE

(*((struct x * )(&b[0]))) = (*((struct x * )(&a[0])));

o

(*((struct x * )(b))) = (*((struct x * )(a)));

Uno comunque potrebbe chiedersi perchè dover usare costrutti come quello di prima e non il semplice ciclo.
Ottimizzazione velocità ....
Se uno scrive gestionale certi tipi di ottimizzazioni può anche evitarle...se invece fai sistemi di compuetr vision allora tutto quello che ottimizza diventa necessario.....
GUarda qui ad esempio :

int a[5] = { 1,2,3,4,5 };
int b[5];


void copia(void)
{
struct p { int f[5]; };
(*((struct p *)(b))) = (*((struct p *)(a)));
}
tradotto dal compilatore con l'opzione Fa

TITLE test1.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC _a
_DATA SEGMENT
COMM _b:DWORD:05H
_a DD 01H
DD 02H
DD 03H
DD 04H
DD 05H
_DATA ENDS
PUBLIC _copia
_TEXT SEGMENT
_copia PROC NEAR
; File test1.c
; Line 6
push ebp
mov ebp, esp
push esi
push edi
; Line 8
mov ecx, 5
mov esi, OFFSET FLAT:_a
mov edi, OFFSET FLAT:_b
rep movsd
; Line 9
pop edi
pop esi
pop ebp
ret 0
_copia ENDP
_TEXT ENDS
END
int a[5] = { 1,2,3,4,5 };
int b[5];
int i;

void copia(void)
{
for(i=0;i!=5;i++)
b[i] = a[i];
} TITLE test2.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC _a
_DATA SEGMENT
COMM _b:DWORD:05H
COMM _i:DWORD
_a DD 01H
DD 02H
DD 03H
DD 04H
DD 05H
_DATA ENDS
PUBLIC _copia
_TEXT SEGMENT
_copia PROC NEAR
; File test2.c
; Line 6
push ebp
mov ebp, esp
; Line 7
mov DWORD PTR _i, 0
jmp SHORT $L35
$L36:
mov eax, DWORD PTR _i
add eax, 1
mov DWORD PTR _i, eax
$L35:
cmp DWORD PTR _i, 5
je SHORT $L37
; Line 8
mov ecx, DWORD PTR _i
mov edx, DWORD PTR _i
mov eax, DWORD PTR _a[edx*4]
mov DWORD PTR _b[ecx*4], eax
jmp SHORT $L36
$L37:
; Line 9
pop ebp
ret 0
_copia ENDP
_TEXT ENDS
END

LoryOne
24-04-2007, 11.36.26
Bene, questo post dovrebbe essere messo in coda ai precedenti, quando abbiamo trattato in maniera molto più soft i puntatori.
Flavio incita ad apprendere anche Assembly, giustamente. ;)

Vorrei far notare il primo codice Assembly , quello della copia del blocco della struttura in un' unica riga di codice in C.
memcpy o CopyMemory effettuano esattamente la stessa cosa.

Ottimizzazione delle risorse e velocità d'esecuzione dovrebbero sempre essere rispettate, qualunque sia il contesto in cui l' applicazione deve "girare". E' chiaro che dal lato Assembly, l'architettura del processore e dei vari registri messi a disposizione del programmatore possono influire sull'incremento prestazionale, così come allocare dati in locazioni di memoria pari oppure dispar,i in base alla tipologia ed alla quantità dei bytes da indirizzare

Ottimo post. (Y) :)

Flavio58
24-04-2007, 13.43.58
Un altro esempio per fare capire che alla fine un puntatore è di fatto un indirizzo è quello che alla fine è alla base del concetto di buffer overflow nelle tecniche di hacking.
Se noi prendiamo un programma e lo guardiamo con il debug in modalità esadecimale possiamo vedere le istruzioni binarie come se fossero degli array di numeri.
Ad esempio un programmino molto corto chiamato boot.com che non faceva altro che resettare la macchina può essere visto in modalità esadeciomale ed essere assegnato ad un array di numeri interi.


unsigned char array[] = {
/* ---- [CODICE DI BOOT.COM] ---- */
0xFB,0x31,0xDB, /* FB STI */
0x8E,0xDB,0xBB, /* 31DB XOR BX,BX */
0x72,0x04,0xB8, /* 8EDB MOV DS,BX */
0x34,0x12,0x89, /* BB7204 MOV BX,0472 */
0x07,0xEA,0x00, /* B83412 MOV AX,1234 */
0x00,0xFF,0xFF /* 8907 MOV [BX],AX */
/* EA0000FFFF JMP FFFF:0000 */
/* ------------------------------- */
};


Ora se dichiarassimo un puntaore a funzione in pratica avremmo uno spazio in memoria per contenere un indirizzo, che si suppone essere di una funzione.
Il C possiede il concetto di CAST o forzatura.
In pratica un certo tipo viene forzato ad assumere le proprietà di un altro tipo.
Per cui se noi avessimo il puntatore a funzione :

void (far *funct)()

&array[0] o semplicemente array (gli array il c li vede come indirizzi) sarebbe a questo punto l'indirizzo del primo elemernto del vettore.
Ma se mediante un cast noi lo assegnassimo al puntaore a funzione con :

void (*funct)() = (void(*)()) array;

costringeremmo il C a vedere funct come un puntatore a funzione il cui indirizzo di fatto è quello del primo elemento dell'array.
Ma una funzione può essere richiamata !
E quindi ....

(*funct)();

non farebbe altro che chiamare la funziopne all'indirizzo &array[0].
Il C salterebbe a questo indirizzo convinto che sia una funzione e quindi eseguirebbe i codici all'interno che di fatto sono querlli di boot.com ....
Detto in altre parole: come fare eseguire al sistema dei numeri esadecimali (convincendolo che sono codici operativi binari) !!!