Perché aumentare i puntatori?


25

Di recente ho iniziato a studiare il C ++ e, dato che la maggior parte delle persone (secondo quanto ho letto), sto lottando con i puntatori.

Non nel senso tradizionale, capisco cosa sono, e perché vengono utilizzati e come possono essere utili, tuttavia non riesco a capire in che modo i puntatori di incremento sarebbero utili, qualcuno può fornire una spiegazione di come incrementare un puntatore è un concetto utile e idiomatico C ++?

Questa domanda è arrivata dopo che ho iniziato a leggere il libro A Tour of C ++ di Bjarne Stroustrup, mi è stato consigliato questo libro, perché ho una certa familiarità con Java, e i ragazzi di Reddit mi hanno detto che sarebbe stato un buon libro di "passaggio" .


11
Un puntatore è solo un iteratore
Charles Salvia,

1
È uno degli strumenti preferiti per scrivere virus informatici che leggono ciò che non dovrebbero leggere. È anche uno dei casi più comuni di vulnerabilità nelle app (quando si sposta un puntatore oltre l'area in cui dovrebbero, quindi si legge o si scrive)> Vedi il bug HeartBleed.
Sam

1
@vasile Questo è ciò che è male sui puntatori.
Cruncher

4
La cosa bella / cattiva di C ++ è che ti permette di fare molto di più prima di chiamare un segfault. Di solito si ottiene un segfault quando si tenta di accedere alla memoria di un altro processo, memoria di sistema o memoria dell'app protetta. Qualsiasi accesso all'interno delle normali pagine dell'applicazione è consentito dal sistema e spetta al programmatore / compilatore / linguaggio imporre limiti ragionevoli. Il C ++ ti permette praticamente di fare quello che vuoi. Per quanto riguarda openssl avere il proprio gestore di memoria - non è vero. Ha solo i meccanismi di accesso alla memoria C ++ predefiniti.
Sam

1
@INdek: otterrai un segfault solo se la memoria a cui stai tentando di accedere è protetta. La maggior parte dei sistemi operativi assegna la protezione a livello di pagina, quindi di solito puoi accedere a tutto ciò che si trova sulla pagina su cui inizia il puntatore. Se il sistema operativo utilizza una dimensione di pagina 4K, si tratta di una discreta quantità di dati. Se il tuo puntatore inizia da qualche parte nell'heap, chiunque può indovinare quanti dati potresti accedere.
TMN

Risposte:


46

Quando si dispone di un array, è possibile impostare un puntatore in modo che punti a un elemento dell'array:

int a[10];
int *p = &a[0];

Qui ppunta al primo elemento di a, che è a[0]. Ora puoi incrementare il puntatore in modo che punti all'elemento successivo:

p++;

Ora ppunta al secondo elemento, a[1]. Puoi accedere all'elemento qui usando *p. Questo è diverso da Java dove dovresti usare una variabile indice intera per accedere agli elementi di un array.

Incrementare un puntatore in C ++ in cui quel puntatore non punta a un elemento di un array è un comportamento indefinito .


23
Sì, con C ++ sei responsabile di evitare errori di programmazione come l'accesso al di fuori dei limiti di un array.
Greg Hewgill,

9
No, incrementare un puntatore che punta a qualsiasi cosa tranne un elemento array è un comportamento indefinito. Tuttavia, se stai facendo qualcosa di basso livello e non portatile, incrementare un puntatore di solito non è altro che accedere alla prossima cosa in memoria, qualunque cosa accada.
Greg Hewgill,

4
Vi sono alcune cose che sono o possono essere trattate come una matrice; una stringa di testo è, in effetti, una matrice di caratteri. In alcuni casi, un int lungo viene trattato come una matrice di byte, sebbene ciò possa facilmente metterti nei guai.
AMADANON Inc.,

6
Ciò indica il tipo , ma il comportamento è descritto in 5.7 Operatori additivi [expr.add]. In particolare, 5.7 / 5 afferma che andare ovunque all'esterno dell'array, tranne uno alla fine, è UB.
Inutile

4
L'ultimo paragrafo è: Se sia l'operando del puntatore che il risultato puntano a elementi dello stesso oggetto array, la valutazione non deve produrre un overflow; altrimenti il ​​comportamento non è definito . Quindi, se il risultato non è né nell'array né oltre la fine, ottieni UB.
Inutile

37

Incrementare i puntatori è C ++ idiomatico, perché la semantica dei puntatori riflette un aspetto fondamentale della filosofia progettuale alla base della libreria standard C ++ (basata sullo STL di Alexander Stepanov )

Il concetto importante qui è che l'STL è progettato attorno a contenitori, algoritmi e iteratori. I puntatori sono semplicemente iteratori .

Ovviamente, la capacità di incrementare (o aggiungere / sottrarre) i puntatori risale a C. Molti algoritmi di manipolazione della stringa C possono essere scritti semplicemente usando l'aritmetica del puntatore. Considera il seguente codice:

char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));

Questo codice utilizza l'aritmetica del puntatore per copiare una stringa C terminata con null. Il ciclo termina automaticamente quando incontra il null.

Con C ++, la semantica del puntatore è generalizzata al concetto di iteratori . La maggior parte dei contenitori C ++ standard fornisce iteratori, a cui è possibile accedere tramite le funzioni begine endmember. Gli iteratori si comportano come puntatori, in quanto possono essere incrementati, dereferenziati e talvolta decrementati o avanzati.

Per iterare su un std::string, dovremmo dire:

std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;

Aumentiamo l'iteratore proprio come vorremmo incrementare un puntatore a una semplice stringa a C. Il motivo per cui questo concetto è potente è perché è possibile utilizzare modelli per scrivere funzioni che funzioneranno per qualsiasi tipo di iteratore che soddisfi i requisiti di concetto necessari. E questo è il potere dell'STL:

std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));

Questo codice copia una stringa in un vettore. La copyfunzione è un modello che funzionerà con qualsiasi iteratore che supporti l'incremento (che include puntatori semplici). Potremmo usare la stessa copyfunzione su una semplice stringa C:

   const char* s1 = "abcdef";
   std::vector<char> buf;
   std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));

Potremmo utilizzare copysu un std::mapo un std::seto qualsiasi contenitore personalizzato che supporti gli iteratori.

Si noti che i puntatori sono un tipo specifico di iteratore: iteratore ad accesso casuale , il che significa che supportano l'incremento, il decremento e l'avanzamento con l' operatore +e -. Altri tipi di iteratori supportano solo un sottoinsieme di semantica del puntatore: un iteratore bidirezionale supporta almeno l'incremento e il decremento; un iteratore in avanti supporta almeno l'incremento. (Tutti i tipi di iteratore supportano il dereferenziamento.) La copyfunzione richiede un iteratore che supporti almeno l'incremento.

Puoi leggere i diversi concetti di iteratore qui .

Pertanto, incrementare i puntatori è un modo C ++ idiomatico per iterare su un array C o accedere a elementi / offset in un array C.


3
Sebbene io usi i puntatori come nel primo esempio, non ci ho mai pensato come iteratore, ora ha molto senso.
coloranti

1
"Il ciclo termina automaticamente quando incontra il null." Questo è un linguaggio terrificante.
Charles Wood,

9
@CharlesWood, allora suppongo che dovresti trovare C piuttosto terrificante
Siler

7
@CharlesWood: l'alternativa è usare la lunghezza della stringa come variabile di controllo del loop, il che significa attraversare la stringa due volte (una volta per determinare la lunghezza e una volta per copiare i caratteri). Quando si esegue un PDP-7 da 1 MHz, può davvero iniziare a sommarsi.
TMN

3
@INdek: prima di tutto, C e C ++ cercano di evitare a tutti i costi di introdurre cambiamenti di rottura - e direi che alterare il comportamento predefinito dei letterali di stringa sarebbe una vera modifica. Ma soprattutto, le stringhe con terminazione zero sono solo una convenzione (reso facile da seguire dal fatto che i valori letterali con stringa sono terminati da zero per impostazione predefinita e che le funzioni della libreria li prevedono), nessuno ti impedisce di utilizzare le stringhe contate in C - in realtà, diverse librerie C le usano (vedi ad esempio BSTR di OLE).
Matteo Italia,

16

L'aritmetica del puntatore è in C ++ perché era in C. L'aritmetica del puntatore è in C perché è un linguaggio normale nell'assemblatore .

Esistono molti sistemi in cui "registro incrementale" è più veloce di "carica valore costante 1 e aggiungi al registro". Inoltre, alcuni sistemi consentono di "caricare DWORD in A dall'indirizzo specificato nel registro B, quindi aggiungere sizeof (DWORD) a B" in un'unica istruzione. In questi giorni potresti aspettarti che un compilatore ottimizzato risolva questo problema per te, ma questa non era davvero un'opzione nel 1973.

Questo è fondamentalmente lo stesso motivo per cui le matrici C non sono controllate da limiti e le stringhe C non hanno una dimensione incorporata in esse: il linguaggio è stato sviluppato su un sistema in cui contavano ogni byte e ogni istruzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.