Esercizio - Aritmetica dei puntatori (primo appello 2021-22, teoria)
In questa pagina 3
Domanda aperta 3 del primo appello 2021-22 (25 gennaio 2022) del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. La stessa domanda è ricomparsa nel quarto appello 2022-23: Esercizio - Aritmetica dei puntatori (quarto appello 2022-23, teoria), con altri esempi. Teoria: C++ - puntatori, array e castun puntatore è una variabile che contiene un indirizzo di memoria e sa il tipo dell'oggetto puntato (T*); si ottiene l'indirizzo con & e si accede al dato con * (dereference, un lvalue), nullptr indica nessun oggetto e due puntatori a tipi diversi sono tipi diversi; void* è un puntatore a memoria grezza (accetta qualsiasi puntatore ma non si dereferenzia) e i cast (static_cast, reinterpret_cast, const_cast) servono per interfacciarsi con hardware e codice non modificabile; il puntatore, a differenza della reference, si può spostare, può essere nullptr e va dereferenziato, e assegnarlo copia l'indirizzo (shallow copy) mentre assegnare una reference copia l'oggetto; un array C è una sequenza contigua di elementi dello stesso tipo, con dimensione costante, senza controllo dei limiti, che passato a una funzione decade in un puntatore al primo elemento; l'aritmetica dei puntatori somma o sottrae interi spostando di n elementi (non di n byte) e la differenza fra puntatori è in elementi; gli errori classici sono nullptr, puntatori non inizializzati, accessi fuori dai limiti e puntatori a oggetti usciti dallo scope.C++ - puntatori, array e cast →.
Testo. Definire l'aritmetica dei puntatori illustrando: 1) il concetto alla base dell'aritmetica; 2) con quali operatori e operandi funziona; 3) due esempi di funzionamento, ciascuno corredato da codice che deve essere commentato mediante descrizione e/o grafici.
1. Il concetto
Un puntatore T* contiene un indirizzo e conosce il tipo T dell'oggetto a cui punta, quindi la sua dimensione sizeof(T). L'aritmetica dei puntatori sfrutta questa informazione: sommare o sottrarre un intero a un puntatore lo sposta di slot, cioè di elementi di tipo T e non di byte. In byte lo spostamento è
È utile perché un array è una sequenza di oggetti contigui: partendo dall'indirizzo del primo, con questo meccanismo si raggiunge qualsiasi elemento, e p[i] è per definizione *(p + i). L'aritmetica è sensibile al contesto: lo stesso + 2 sposta di 8 byte su un int* (con int da 4 byte) e di 16 su un double*.
2. Operatori e operandi
| operazione | operandi | risultato |
|---|---|---|
p + n, n + p |
puntatore, intero | puntatore, elementi più avanti |
p - n |
puntatore, intero | puntatore, elementi indietro |
p += n, p -= n |
puntatore, intero | sposta p |
++p, p++, --p, p-- |
puntatore | sposta di un elemento |
q - p |
due puntatori dello stesso tipo, nello stesso array | intero con segno: numero di elementi fra i due (non di byte) |
p == q, p != q, p < q, p > q |
due puntatori dello stesso array | confronto degli indirizzi |
p[i], *(p + i) |
puntatore, intero | l'-esimo elemento (anche ) |
Non esistono: la somma di due puntatori (p + q), il prodotto o la divisione. Le operazioni sono lecite solo restando dentro l'array puntato, più la posizione "uno dopo l'ultimo" (che si può formare e confrontare ma non dereferenziare); fuori è comportamento indefinito.
3. Due esempi
#include <iostream>
using namespace std;
int main()
{
// ESEMPIO 1: scorrere un array spostando il puntatore
int a[5] = {10, 20, 30, 40, 50};
int* p = &a[0]; // oppure p = a (decadimento)
for (int* q = p; q != p + 5; ++q) // p + 5 e' "uno dopo l'ultimo": si puo' confrontare, non dereferenziare
cout << *q << ' ';
cout << '\n';
// ESEMPIO 2: p + n e differenza fra puntatori
int* r = p + 3; // punta a a[3] = 40: avanza di 3 ELEMENTI (3 * sizeof(int) byte)
cout << "*r = " << *r << ", r[-2] = " << r[-2] << '\n'; // r[-2] e' a[1]
cout << "r - p = " << r - p << " elementi, in byte = "
<< reinterpret_cast<char*>(r) - reinterpret_cast<char*>(p) << '\n';
cout << "r > p: " << (r > p) << '\n';
// p + r; // errore: non si sommano due puntatori
// stessa aritmetica con tipi di dimensione diversa
double d[3] = {1.5, 2.5, 3.5};
double* pd = d;
pd += 2; // avanza di 2 double = 16 byte
cout << "*pd = " << *pd << ", sizeof(double) = " << sizeof(double) << '\n';
return 0;
}10 20 30 40 50
*r = 40, r[-2] = 20
r - p = 3 elementi, in byte = 12
r > p: 1
*pd = 3.5, sizeof(double) = 8Esempio 1: scorrere un array con un puntatore. int a[5] = {10, 20, 30, 40, 50}; int* p = &a[0];. Il ciclo for (int* q = p; q != p + 5; ++q) sposta q di un elemento a ogni giro. Il punto p + 5 è "uno dopo l'ultimo": serve da condizione di fine (si confronta, non si dereferenzia). Stampa 10 20 30 40 50.
indice: a[0] a[1] a[2] a[3] a[4] (a[5]: oltre la fine)
valore: 10 20 30 40 50
indirizzo: A A+4 A+8 A+12 A+16 A+20
^ ^
p p + 5Esempio 2: spostamenti e differenza fra puntatori. int* r = p + 3; punta a a[3] : l'indirizzo è . Quindi *r = 40, r[-2] è a[1] (indici negativi: si torna indietro dall'elemento puntato). La differenza r - p vale 3 (elementi), mentre in byte la distanza è (convertendo i puntatori a char* si leggono 12 byte). Con tipi più grandi lo slot cambia: double* pd = d; pd += 2; sposta di 16 byte e punta al terzo elemento, 3.5.
p -> a[0]=10 a[1]=20 a[2]=30 r -> a[3]=40 a[4]=50
<---------- r - p = 3 elementi = 12 byte ---------->Casi limite da conoscere: p + r non compila; *(p + 5) legge fuori dall'array (indefinito); r > p è vero (indirizzo maggiore).
Versione ripasso
- Concetto:
T*ha indirizzo + tipop + nsposta di elementi, cioè byte. Serve per scorrere array contigui:p[i]*(p + i). Dipende dal tipo:+ 2= 8 byte suint*, 16 sudouble*. - Operatori e operandi:
| operazione | operandi | risultato |
|---|---|---|
p ± n, p += n, ++p |
puntatore, intero | puntatore spostato |
q - p |
due puntatori stesso array | numero di elementi (con segno) |
== != < > |
due puntatori stesso array | confronto |
p[i] |
puntatore, intero | elemento (anche negativo) |
Non esiste p + q. Lecito solo dentro l'array e fino a "uno dopo l'ultimo" (non dereferenziabile).
- Esempio 1:
int a[5] = {10,20,30,40,50}; int* p = &a[0]; for (int* q = p; q != p + 5; ++q) cout << *q << ' ';10 20 30 40 50;p + 5= fine. - Esempio 2:
int* r = p + 3;*r = 40,r[-2] = 20,r - p = 3elementi = 12 byte;double* pd = d; pd += 2;16 byte,*pd = 3.5;r > pvero.
p -> 10 20 30 r -> 40 50 (fine = p + 5) r - p = 3 elementi = 3 * sizeof(int) = 12 byte- Errori tipici: credere che
p + 1sposti di un byte; dire cheq - pvale (è ); sommare due puntatori; dereferenziarep + 5; usare l'aritmetica fuori dall'array.