Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Informatica 2 – modulo CMassimo Callisto De Donato
www.cs.unicam.it/massimo.callisto
LEZIONE 7 – PUNTATORI
Università degli studi di CamerinoScuola di scienze e tecnologia - Sezione Informatica
Organizzazione intuitiva della memoria• Ad ogni variabile associamo un nome per poterla utilizzare.
• Dal punto di vista del calcolatore, si utilizza un “indirizzo” di memoria (usualmente rappresentati in valore esadecimale). Esempio (schema semplificato) :
int a;
a = 5;
printf(„„%d‟‟, a);
? ? ? ?
0x120 0x121 0x122 0x123
? ? ? ?
0x120 0x121 0x122 0x123
a
? 5 ? ?
0x120 0x121 0x122 0x123
a
Se volessimo manipolare direttamente gli indirizzi?
I puntatori
• Un indirizzo di memoria può essere assegnato solo ad una categoria speciale di variabili: i puntatori
• Una variabile di tipo puntatore contiene l’indirizzo di memoria di un altra variabile.
• Un puntatore di un certo tipo (e.g. int, float, …) punta ad una variabili delle stesso tipo (e.g. int, float, …). Inoltre si possono a tipi di dati derivati (es: strutture).
• Due aspetti da poter manipolare:
– Il puntatore in sé
– La variabile puntata.
• Vedremo che i puntatori rivestono un ruolo chiave nella gestione dinamica della memoria.
Dichiarare un puntatore
• Sintassi della dichiarazione di un puntatore:tipo* nome_variabile;
tipo *nome_variabile; // equivalente al primo
• Abbiamo definito una variabile(un puntatore) capace di memorizzare indirizzi di memoria di variabili di tipo tipo.
• Parliamo di "indirizzi di memoria di tipo tipo" dato che ogni tipo di dato corrisponde una diversa quantità di memoria (celle) occupata.
• Esempi:
int *ptr; /* si legge: ptr è un puntatore ad intero, ovvero p
contiene l‟indirizzo di un intero */
int *a, *b;
double* dptr;
char *cptr;
Indirizzare un puntatore
• Possiamo inizializzare un variabile puntatore utilizzando l’operatore & che restituisce l’indirizzo di una data variabile. Esempio :
int *p; // p è un puntatore ad intero ma a chi punta?
• Assegniamo a p l'indirizzo di b
int b = 7;
int *p; // p è un puntatore ad intero
p = &b; // p punta a b (p prende l‟indirizzo di b)
• Visivamente (ed in modo intuitivo):
0x121 7 ? ?
0x80 0x121 0x122 0x123
bp
Operatore di indirezione
• L'operatore di indirezione (o deferenziazione) “*”:
permette di ottenere il valore contenuto della locazione di memoria puntata dal puntatore.
• Quindi, anteponendo * al puntatore operiamo sul valore della variabile puntata:
Operatore di indirezione
• Dopo l’assegnamento pi=&a, a e *pi sono degli alias, ossia sono perfettamente equivalenti.
• Che stampiamo con il codice sotto?
Altri esempi/* 1 esempio*/
int *p;
int pass;
p = &pass;
*p = 7;
p = 7; //?? cioè?
/* 2 esempio*/
int c = 1; float d;
int *p;
p = &c;
d = *p; // Ci manca?
/* 3 esempio*/
int c = 1;
int *p ;
p = &c;
(*p)++;
printf(" c = %d",c); // Che stampa?
Altri esempi (unico frammento di codice)
int *p;
int x = 5;
int y;
p = &x;
y = *p;
int *q; //?
q = p;
Inizializzare i puntatori
• Con la dichiarazione di un puntatore, all’inizio a cosa puntiamo?
• Si assume che i puntatori conterranno un indirizzo indefinito (puntano a qualcosa in memoria che non sappiamo).
• Dato che il puntatore è un tipo di dato molto delicato è sempre bene fare una giusta inizializzazione usando:
– Assegnando l’indirizzo di una variabile dichiarata e definita;
– Assegnandogli un altro puntatore (vale solo se sono dello stesso tipo);
– Assegnandogli il valore NULL (costante definita in stddef.h)
I puntatori e array
• In C, un array è una costante all’indirizzo della prima cella di memoria. Questa costante è il puntatore alla 1° cella.
• Possiamo operare sugli array con i puntatori (fondamentale nell’allocazione dinamica di array).
• Ad esempio:char buffer[5]; char *s;
// Le due istruzioni si equivalgono
s = &buffer[0];
s = buffer; // buffer è implementato come un puntatore
I puntatori e array
• Dato che gli array sono elementi sequenziali identificati da un indice di posizione, l'incremento dell'indirizzo del puntatore scorrere anche l'array.
• Esempio:
– s[0] corrisponde al puntatore s nella prima posizione;
– (s + j) è corrisponde al puntatore s alla posizione (j+1)-esimo elemento dell'array. s[0] == (s + 0)
// Equivalenti
I puntatori e arrayint buffer[5];
int s*;
s = buffer;
for(i=0; i<5; i++)
buffer[i] = i;
for(i=0; i<5; i++){
*s = i; // oppure s[i] = i;
s = s + 1;}
• Nota che buffer è una costante implementata come un puntatore.
• Significa che tutte le operazioni di modifica dell’indirizzo puntato non sono valide:
buffer++; // no
s = (buffer + 2); // ok
buffer = (buffer + 2); // no
I puntatori e array: altro esempioint a[5];
int *p;
// nota che p = a ; equivale a p = &a[0]
I puntatori e array: altro esempio/* Che stampiamo? (sempre che sia corretto) */
#include <stdio.h>
main() {
int b[] = {1,2,3};
int *s;
s = b;
printf("%d\n", b[0]); // 1
printf("%d\n", *s); // 1
printf("%d\n", *b); // è *(b + 0)==b[0]
printf("%d\n", *(s+1)); // 2
printf("%d\n", s[1]); // 2
printf("%d\n", *(b + 4)); // out of range
printf("%d\n", *(b++)); // nn si può fare
}
Aritmetica dei puntatori• Nota la differenza:
– (*s)++ -> incrementa di uno il valore della variabile puntata da s
– (s++) -> incrementa l'indirizzo contenuto in s
• Sono valide solo operazioni di l’incremento ++,+ e di decremento --, -(altri operatori non sono giusti)
• Ma di quanto si incrementa l'indirizzo contenuto in s dopo s++?
– Dipende dal tipo di dato puntato da s dato che ogni tipo occupa un numero diverso di elementi in memoria.
– se una cella è di 1 byte, il tipo char occupa 1byte ed int 4 byte, allora:
char *p; // p++ incrementa l‟indirizzo di 1 byte
int *p; // p++ incrementa l‟indirizzo di 4 byte
Aritmetica dei puntatori• Ad esempio, l’assegnamento (1) sposta il puntatore pi in avanti di tre posizioni:
int buffer[5], *pi;
pi = buffer;
pi = pi + 3; (1)
• L’assegnamento (2) sposta il puntatore pi indietro di tre posizioni e punta a buffer[1]:pi = pi - 2; (2)
buffer[4] = *p++; // inserisco p[1] in buffer[4] e p
//diventa p[2]
• E se ora scrivessi come sotto che c’è che non va?c = *(pi – 3); // fuori range
Esempio#include<stdio.h>
main(){
int v1[5];
int v2[5];
int i;
int *pi;
for(i=0; (v1[i] = (i+1)) <= 5; i++);
i = &v1[4]-&v1[2];
printf("%d\n", i); // scrive 2 -> (v + 4) – (v + 2)
i = &v2[4] - &v1[2];
printf("%d\n", i); //v1 è diverso da v2
pi = v2 - 2; // non ha senso
}
Puntatori e passaggio dei parametri per indirizzo
• Supponiamo di voler implementare la funzione swap per lo scambio di valori di due interi ma cosi posso solo scambiare i valori dei parametri formali:
Puntatori e passaggio dei parametri per indirizzo
• In C gli argomenti delle funzioni vengono passati per valore: alterare i valori dei parametri attuali non cambia quelli dei parametri attuali [non vale per gli array].
• Il passaggio per indirizzo permette di applicare le modifiche sui parametri formali anche su quelli attuali: una funzione può modificare una variabile della funzione chiamante.
• Per implementare (simulare) il passaggio per indirizzo usiamo i puntatori come parametri formali:
(Simulare perché…) Passiamo sempre un valore ma questo è un indirizzo. Riferendoci all’indirizzo modifichiamo il valore della variabile puntata.
Puntatori e passaggio dei parametri per indirizzo
• Modificare il contenuto delle locazioni puntate dai puntatori a e b significa chiaramente modificare i valori associati alle variabili x e y
Puntatori e passaggio dei parametri per indirizzo
• Se scrivessimo:void swap(int *a, int *q){
int *tmp;
tmp = a;
a = b;
b = tmp;
}
• E' uguale?void swap(int *a, int *q){
int tmp;
tmp = *a;
*a = *b;
*b = *tmp;
}
• La prima implementazione è la migliore dato che manipoliamo solo indirizzi.
I puntatori e array… e stringhe
• Le stringhe sono implementate in C come vettori di caratteri con in più il terminatore di stringa ‘\0’.
• Dato che gli array sono trattati con i puntatori, le stringhe possono essere manipolate con i puntatori…
Esempio#include<stdio.h>
int my_strlen(char *);
main(){
char str[] = "Una stringa molto lunga";
printf("La lunghezza di %s e' %d\n ", str, my_strlen(str));
}
int my_strlen(char * s){
int i=0;
while (s[i] != '\0') i++; // valeva anche while(s[i++]);
// dato che „\0‟ viene visto come false
return i;
}
/*
Nota che gli array possono essere passati come puntatori. Ovvero:
int my_strlen(char * s); //equivale a
int my_strlen(char s[]);
*/
Esempio: equivalenze (forse)
/* prima versione */
int my_strlen (char *s) {
int n = 0;
while ( *s != '\0') {
n++; s++;
}
return n;
}
/* seconda versione*/
int my_strlen (char *s) {
int n = 0;
while (*s++ != '\0')
n++;
return n;
}
/* terza versione */
int my_strlen (char *s) {
int n = 0;
while (*s++)
n++;
return n;
}
/* quarta versione */
int my_strlen (char *s) {
char *p = s;
while (*s++);
return s - p;
}
Esempio: che stampa?
main() {
char v[6];
int i;
for(i = 0; i < 4; i++)
v[i] = 'B';
v[4] = '\0';
v[5] = 'X';
printf("1: %d\n", my_strlen1(v));
printf("sto a %c\n", *v);
printf("2: %d\n", my_strlen2(v));
printf("sto a %c\n", *v);
printf("3: %d\n", my_strlen3(v));
printf("sto a %c\n", *v);
printf("4: %d\n", my_strlen4(v));
printf("sto a %c\n", *v);
printf("Ufficiale: %d\n", strlen(v));
}
int my_strlen1 (char *s) {
char *p = s;
while (*s++);
printf("1 - sto a %c\n", *s);
return s - p;
}
int my_strlen2 (char *s) {
int n = 0;
while (*s++) n++;
printf("2 - sto a %c\n", *s);
return n;
}
int my_strlen3 (char *s) {
int n = 0;
while (*s++ != '\0„) n++;
printf("3 - sto a %c\n", *s);
return n;
}
int my_strlen4 (char *s) {
int n = 0;
while ( *s != '\0„)
{ n++; s++; }
printf("4 - sto a %c\n", *s);
return n;
}
Esempio/* Che stampa?? */
#include <stdio.h>
#include <limits.h>
int my_strlen (char *s) {
char *p = s;
while (*s++);
return s - p;
}
int my_strlenInt (int *s) {
int *p = s;
while (*++s != INT_MIN);
return s - p;
}
main() {
int b[] = {1,2,3,INT_MIN};
char s[] = "max";
printf("my_strlen ->%d\n", my_strlen(s));
printf("my_strlen2->%d\n", my_strlenInt(b));
}
// Perché abbiamo usato INT_MIN?
Esempio di sostituzione/* Che stampa?? */
#include <stdio.h>
void func(char *, char *);
int main ()
{
char a[] = "e' una prova";
char b[] = "stringa da sostituire";
printf ("'%s'\n'%s'\n\n",a, b);
func(a, b);
printf ("'%s'\n'%s'\n\n",a, b);
return 0;
}
void func(char *p, char *q){
while (*p != '\0'){
*q = *p;
p++; q++;
}
*q='\0'; // dobbiamo immettere un nuovo terminatore in b[]
}
I puntatori e array… e stringhe• Nota che questo codice genera un errore nel programma:
char *p;
p = "stringa";
*(p + 2) = 't'; // non ammesse modifiche
p = p + 2; // ok. Possiamo spostarci p”ringa”
• La sequenza "stringa" è una costante puntata da p che può essere solo letta e su cui ci possiamo spostare. Cosa diversa (e corretta) è questa:
char s[] = "stringa";
char *p = s;
*(p + 2) = 't';
Altri esempi: che facciamo?/* ?? */
char s[] = "prova";
/* ?? */
char s[] = {‟p‟, ‟r‟, ‟o‟, ‟v‟, ‟a‟, 0};
/* ?? */
char c, *t;
/* ?? */
c = *s;
/* ?? */
t = s + 2;
/* ?? */
s[0] = ‟t‟;
/* ?? */
char *name = "arturo";
/* ?? */
char surname[] = "rossi";
/* ?? */
name++;
/* ?? */
surname++;
Altri esempi: che facciamo?/* un vettore di 6 caratteri, compreso il terminatore */
char s[] = "prova";
/* lo stesso, in notazione diversa ma con un errore */
char s[] = {‟p‟, ‟r‟, ‟o‟, ‟v‟, ‟a‟, \0};
/* un carattere, un puntatore a carattere… però meglio spezzare */
char c, *t;
/* c prende il valore ‟p‟ */
c = *s;
/* t rappresenta la stringa "ova" */
t = s + 2;
/* ora la stringa s `e "trova" */
s[0] = ‟t‟;
/* un puntatore ad un‟area prei-inizializzata di 7 byte */
char *name = "arturo";
/* un‟area di 6 byte, con indirizzo "surname" */
char surname[] = "rossi";
/* ora name indica "rturo" */
name++;
/* errore: surname ha un indirizzo costante che non può essere modificato */
surname++;
In sintesi• Un puntatore è una variabile che contiene un indirizzo di memoria (della variabile
a cui punta). Sintassi:tipo* nome_ptr; oppure tipo * nome_ptr;
• Assegnamento di un puntatore: possiamo far puntare un puntatore ad una variabile assegnandogli l'indirizzo con &. Sintassi:
– ptr = &x; dove ptr è un puntatore al tipo a cui appartiene x
– vale anche: ptr = pr; dove prt e pt sono puntatori dello stesso tipo
• Accesso alla variabile puntata tramite l’operatore di indirezione *. Sintassi: – *ptr restituisce il valore della variabile puntata da ptr
• I puntatori possono essere incrementati (++, +) e decrementati (--, -); Si usano con gli array…
In sintesi• Puntatori ed array: il nome di un array contiene una costante che è l’indirizzo alla
1° elemento:char buffer[5], *s;
s = buffer; // equivalente a s = &buffer[0]
buffer++; // no. L‟indirizzo di buffer è costante, s++ è ok
• L’equivalenza è data dal fatto che buffer[i]e *(buffer+i) sono uguali:for (i=0; i < n; i++) buffer[i] = „K‟;
for (i=0; i < n; i++) *(s + i) = „K‟;
• ++ o -- sono ragionevoli solo su array e se la posizione corrente è corretta:s = buffer;
s++; // ok
s = buffer;
s--; // non va bene. Out of range su buffer
In sintesi• Puntatori e passaggio per riferimento: usiamo i puntatori realizzare il passaggio di
indirizzi.
• Operando tramite indirizzi, l'alterazione dei parametri formali modifica anche i parametri attuali!
• Usando i puntatori nei parametri formali e passando l'indirizzo del parametro attuale (con &) si realizza il passaggio per riferimento.
#include <stdio.h>
void toLowerP(char *);
void toUpperP(char *);
main(){
char c = getchar();
printf(“%c\n”, c);
toLowerP(&c); //
printf(“%c\n”, c);
toUpperP(&c); //
printf(“: %c\n”, c);
}
void toLowerP(char *cptr){
if(*cptr compreso in [A-Z])
*cptr = ?? + 32;
}
void toLowerP(char *cptr){
if(*cptr compreso in [a-z])
*cptr = ?? – 32;
}
In sintesi
#include <stdio.h>
void inizializza(int *, int);
main(){
int c[10];
inizializza(c, 10);
}
void inizializza(int* pt, int n)
{
int i;
for (i=0; i< n; i++){
printf(„„Inserisci elemento: ‟‟);
scanf(„„%d‟‟, pt[i]);
//in alternativa: scanf(„„%d‟‟, (pt + i));
}
}
Perché qui non c’è ‘&’?
In sintesi
• Perche scriviamo in questo modo?
int i, n;
float x;
char name[50];
n = scanf("%d%f%s", &i, &x, name);
Qualche errore che si commette:• Mancato indirizzamento del puntatore e versione corretta:
• Operazioni concesse ma sbagliate:
int x;
int *p;
float *t;
x = p; // x contiene un indirizzo di memoria
t = x; // ci manca la & però x è anche un int
t = p; // ma p è un int!
Qualche errore che si commette:
• Assunzioni riguardo posizioni in memoria di array “adiacenti”
• Fare assunzioni riguardo alle posizioni di variabili in memoria. Ad ogni esecuzione la posizione usualmente cambia.
• L’output di sotto non è sempre lo stesso:
Ancora sui puntatori…
• Puntatori e funzioni
• Allocazione dinamica della memoria
• Array di puntatori
• Indirizzamento multiplo
• …
Puntatori e funzioni
• Abbiamo visto i puntatori ed il passaggio per indirizzo simulano il passaggio per riferimento.
• E’ naturale aspettarsi che una funzione ritorni un puntatore.
#include <stdio.h>
char* myfunc(char *str){
int i = 0;
while(str++)
if(*str == 's')
return str;
return NULL;
}
main()
{
char str[] = "massimo";
printf("%s", myfunc(str));
}
Puntatori e funzioni.. ed esercizi• Tornando un puntatore, possiamo far puntare ad una locazione di
memoria che può essere l’inizio di una sequenza (es. un array, strutture)
• Per esercizio, implementare i seguenti prototipi:
/* ritorna la stringa fino al primo carattere maiuscolo, null se non c'è */
char* fromFirstUpper(char *);
/* ritorna la stringa fino al primo numero, null se non c'è */
char* fromFirstDigit(char *);
/* ritorna la stringa di soli numeri, null se non c'è */
char* parseDigits(char *);
/* torna una stringa a partire da s1 senza i caratteri compresi in cs */
char* parseChars(char* s1, char* cs);
/* torna la parte di stringa in s1 che fa match con s2, null se non c'è */
char* mySubString(char* s1, char* s2);
/* torna i valori di vet i cui elementi sono > limit, null se nn ci sono */
int* greatesValues (int* vet, int limit);
/* trova la posizione della 1 occorrenza di c in s1, -1 se non c‟è*/
int firstOccurrenceOf (char* s1, char c);
Allocazione dinamica• In C, la memoria può essere gestita staticamente, automaticamente o
dinamicamente.
– Gestione statica: le variabili sono allocate ed esistono dall'inizio fino alla fine del programma (e.g. variabili globali, static).
– Gestione automatica: sono allocate e distrutte durante l'esecuzione. Ad esempio, l’allocazione automatica per le variabili locali ad una funzione.
• In tutti e due i casi la memoria viene occupata in dipendenza alle variabili ed alle funzioni utilizzate.
• In molte situazioni vogliamo più flessibilità (es. in casi dove l’input è variabile).
• Parliamo quindi di gestione dinamica della memoria, ovvero le tecniche attraverso le quali un programma ottiene memoria durante la sua esecuzione (run-time).
Allocazione dinamica
• Possiamo allocare dinamicamente la memoria (malloc()) e quindi de-allocarla (free()).
#include <stdlib.h>
void *malloc(size_t size);
• L’utilizzo tipico è il seguente:punt = (tipodato *)malloc(sizeof(tipodato));
– tipodato è il tipo di memoria che abbiamo richiesto
– punt è una variabile di tipo tipodato *
– sizeof() serve a calcolare il corretto numero di bytes che occupa tipodato
Allocazione dinamica: void *punt = (tipodato *)malloc(sizeof(tipodato));
– tipodato è il tipo di memoria che abbiamo richiesto
– punt è una variabile di tipo tipodato *
– sizeof() serve a calcolare il corretto numero di bytes che occupa tipodato
• Se la memoria c’è, otteniamo il puntatore al suo inizio (punt); significa che possiamo scorrere punt in quell’area senza far danni.
• Se la memoria non è disponibile otteniamo un puntatore a NULL.
• Perché abbiamo usato un casting (tipodato *)?
– Perché main torna un puntatore generico void * che può puntare a qualsiasi tipo di dato.
– La conversione che facciamo è sicura (senza rischi di perdite) dato che sappiamo già quanta memoria abbiamo richiesto (e ottenuta).
Esempio
• Allochiamo dinamicamente un intero:
#include <stdio.h>
#include <stdlib.h>
main()
{
int *p;
p = (int *)malloc(sizeof(int));
if(p == NULL) exit(-1);
*p = 12;
(*p)++; // *p++ -> ??
printf("*p vale %d\n", *p); // stampa?
}
La funzione exit(val) termina immediatamente il programma.
Al programma chiamante (es. il S.O.) ritorna val per comunicare la condizione di terminazione
Esempio/*Assunzioni sbagliate*/
#include <stdio.h>
main()
{
int *p;
p = (int *)malloc(sizeof(int));
// prima potevamo scrivere anche:
p = (int *)malloc(4);
// ma poteva essere un assunzione sbagliata.
// Es.: il seguente output non è sempre predicibile
printf("Tutto in byte (n byte = (2^(n*8))-1 bits)...\n");
printf("int: %d\n", sizeof(int));
printf("char: %d\n", sizeof(char));
printf("float: %d\n", sizeof(float));
printf("long double: %d\n", sizeof(long double));
printf("short int: %d\n", sizeof(short int));
printf("long int: %d\n", sizeof(long int));
}
Esempio• Array dinamico:
/*soliti includes*/
main()
{
int i, *p;
/* allochiamo 10 elementi. Significa che se ci muoviamo nello spazio di p per 10 posizioni allora non faremo mai danni */
p = (int *)malloc(10*sizeof(int));
if(p == NULL) exit(-1);
for(i = 0; i < 10; i++)
*(p + i) = i;
for(i = 0; i < 10; i++)
printf("*(p + %d) vale %d\n", i, *(p + i)); // stampa?
p = (int *)malloc(20*sizeof(int)); // 20 elementi
if(p == NULL) exit(-1);
for(i = 0; i < 20; i++)
*(p + i) = i*2;
for(i = 0; i < 20; i++)
printf("*(p + %d) vale %d\n", i, *(p + i)); // stampa?
}
Esempio
• Che facciamo?
/*che includes ci vanno??*/
main()
{
int *p;
p = (int *) malloc (INT_MAX * sizeof(int));
// Sempre bene controllare
if (p == NULL){
printf (“Mmmm\n") ;
exit(-1);
}
}
Esempio• Che facciamo?
int* request_mem(void)
{
unsigned num; // Perché?
int *ptr;
printf("Enter the number of type int to allocate: ");
scanf("%u", &num);
ptr = (int *)malloc(num*sizeof(int));
if (ptr != NULL){
printf("Memory allocation was successful.");
return ptr;
}else{
printf("Memory allocation failed.");
return NULL;
}
}
Esempio#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* my_concat(const char *,const char *),
main (void)
{
char *p;
p = concat("abcd","efghi");
printf ("risultato %s", p); // stampa?
}
char* my_concat(const char *s1, const char *s2){
char * result;
result = (char *) malloc((strlen (s1) + strlen (s2) + 1) * sizeof(char));
if (result == NULL) {
printf (“Out of memory\n");
exit (-1);
}
strcpy (result, s1);
strcat (result, s2);
return result;
}
La keyword const asserisce che nessuna modifica nel valore del parametro è possibile.
Utile nel passaggio per riferimento.
Esempio su const/* otteniamo un errore di compilazione */
#include <stdio.h>
void _stampa(const char * s){
*(s+2) = 'F';
printf("%s\n", s);
}
main() {
char s[] = "stringa";
_stampa(s);
}
Allocazione dinamica: de-allocazione• free: de-alloca memoria precedentemente ottenuta con una malloc:
void* free(void* p);
• Si richiama sempre su puntatori restituiti da una malloc.
• free si rende necessaria dato che la memoria ottenuta con mallocrimane riservata fino alla fine del programma.
• Nota che free torna void*: dopo lo chiamata p punta a qualcosa di indefinito ed è bene farlo puntare manualmente a NULL:
int* p = (int *)malloc(sizeof(int));
int* q;
…
free(p);
free(q); // scorretto
p = NULL; // stiamo sicuri su p
ora p punta ad una posizione
indefinita ovvero possiamo
fare ancora danni!
Esempio: frammento di codice/* che facciamo in ogni istruzione? */
int *pi;
int *pj;
pi = (int *)malloc(sizeof(int));
*pi = 150;
pj = pi;
free(pj);
pi = (int *)malloc(sizeof(int));
*pi = 4000;
pj = (int *)malloc(sizeof(int));
*pj = *pi;
pj = (int *)malloc(sizeof(int));
/* ALLA FINE ABBIAMO PERSO UNA CELLA DI MEMORIA con l‟ultima malloc() */
Nota che abbiamo ucciso anche pi
e non leggeremmo più 150!
Esempio: frammento di codice/* Anche qui perdiamo qualcosa. Dove e perché? */
/* includes…*/
main(){
int *p, *q ;
int x, y;
x=5;
y=14;
p=(int *)malloc(sizeof(int));
q=(int *)malloc(sizeof(int));
*p = 25;
*q = 30;
*p = x;
y = *q;
p = &x; // ho perso la memoria puntata da p in preedenza
}
Esempio: frammento di codice/* Se copiassimo e compilassimo di sana pianta questo codice … Cosa
c‟è che non va bene? */
main(){
printf(“Inserisci la lunghezza del vettore ”);
scanf(“%f”, &n);
int A[n];
int *a;
a = (int *)malloc (n * sizeof(int));
for (i=0; i<n; i++)
a[i] = 0;
free (a);
a = NULL;
free(A);
}
Array di puntatori
• I puntatori possono essere disposti su un array:int* a[5], x = 10;
a[0] = &x;
printf("%d\n", *a[0]);
printf("%d\n", *a[2]); // ok??
• Altri esempi:char* a[5];
float* b[MAX_INT];
char* colori[3] = {"Blu", "Rosso", "Verde"};
Array di puntatori
• char* colori[3] = {"Blu", "Rosso", "Verde"}; definisce un array multi-dimensionale (2 in questo caso) inizializzato con 3 array di stringhe. E’ uno dei casi più comuni:
void syntax error(int num){
char* err[] = {
"Impossibile aprire il file",
"Errore in lettura",
"Errore in scrittura",
"Guasto al dispositivo"
};
if (num < 4)
printf("%s\n", err[num]);
else
printf("%d non valido", num);
}
Indirizzamento multiplo
• Se un puntatore contiene l’indirizzo di un “tipo di variabile”, quel tipo può essere a sua volta di tipo puntatore e parliamo di indirizzamento multiplo.
int x, *P, **DP;
P = &x;
DP = &P;
**DP=1518;
• Altro esempio:
int x = 10;
int *p = &x; int **q = &p;
printf("%d\n", **q);
• Che significa? int x; int * a[10]; *a[0] = &x; **a=10;
Indirizzamento multiplo
int **ipp;
int i = 5, j = 6, k = 7;
int *ip1 = &i;
int *ip2 = &j;
ipp = &ip1;
*ipp = ip2;
*ipp = &k;
Indirizzamento multiplo/* Che succede? */
main() {
int a;
int *p;
int **pp;
a=9;
p=&a;
pp=&p;
printf("Indirizzo di pp=%d, valore=%d\n", &pp, pp);
printf("Indirizzo di p=%d, valore=%d\n", &p, p);
printf("Indirizzo di a=%d, valore=%d \n", &a, a);
}
Indirizzamento multiplo
• Allocazione dinamica di un array bidimensionale 2 x 3.
int **a;
int i;
a = (int **) malloc (2 * sizeof (int *));
…
for (i=0; i<2; i++)
a[i] = (int *) malloc (3 * sizeof (int));
…
a [0][1] = 15;
Indirizzamento multiplo
• Ora sappiamo leggere meglio il prototipo del
main?
int main(int argc, char *argv[]);
int main(int argc, char **argv);
Indirizzamento multiplo#include <stdio.h>
// int main (int argc, char *+argv)
int main (int argc, char *argv[])
{
int count;
// argc è sempre > 0 argv[0] è sempre corretto
printf ("Nome del programma '%s'\n",argv[0]);
if (argc > 1)
for (count = 1; count < argc; count++)
printf("argv[%d] = %s\n", count, argv[count]);
else
printf("Nessun parametro passato.\n");
return 0;
}
/* nota che argc e argv sono nomi che si usano per convenzione. Potevamo
anche usare altri nomi. */