Quanti livelli di puntatori possiamo avere?


444

Quanti puntatori ( *) sono consentiti in una singola variabile?

Consideriamo il seguente esempio.

int a = 10;
int *p = &a;

Allo stesso modo possiamo avere

int **q = &p;
int ***r = &q;

e così via.

Per esempio,

int ****************zz;

582
Se questo dovesse mai diventare un vero problema per te, stai facendo qualcosa di molto sbagliato.
ThiefMaster,

279
Puoi continuare ad aggiungere livelli di puntatori fino a quando il cervello non esplode o il compilatore si scioglie, a seconda di quale evento si verifichi prima.
JeremyP,

47
Poiché un puntatore a un puntatore è di nuovo, beh, solo un puntatore, non dovrebbe esserci alcun limite teorico. Forse il compilatore non sarà in grado di gestirlo oltre un limite ridicolmente alto, ma beh ...
Christian Rau,

73
con il più recente c ++ dovresti usare qualcosa di similestd::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx

44
@josefx - questo mostra un problema nello standard C ++ - non c'è modo di aumentare i puntatori intelligenti ai poteri. Dobbiamo richiedere immediatamente un'estensione per supportare, ad esempio, (pow (std::shared_ptr, -0.3))<T> x;per -0.3 livelli di riferimento indiretto.
Steve314,

Risposte:


400

Lo Cstandard specifica il limite inferiore:

5.2.4.1 Limiti di traduzione

276 L'implementazione deve essere in grado di tradurre ed eseguire almeno un programma che contenga almeno un'istanza di ciascuno dei seguenti limiti: [...]

279 - 12 dichiaratori di puntatori, array e funzioni (in qualsiasi combinazione) che modificano un tipo aritmetico, struttura, unione o vuoto in una dichiarazione

Il limite superiore è specifico dell'implementazione.


121
Lo standard C ++ "raccomanda" che l'implementazione supporti almeno 256. (La leggibilità raccomanda di non superare 2 o 3, e anche allora: più di una dovrebbe essere eccezionale.)
James Kanze,

22
Questo limite riguarda quanti in una singola dichiarazione; non impone un limite superiore a quanta indiretta si può ottenere tramite più typedefs.
Kaz,

11
@Kaz - sì, è vero. Ma dal momento che la specifica non specifica (un gioco di parole) che specifica un limite inferiore obbligatorio, è il limite superiore effettivo che tutti i compilatori conformi alle specifiche sono tenuti a supportare. Naturalmente potrebbe essere inferiore al limite superiore specifico del fornitore. Riformulato in modo diverso (per allinearlo alla domanda del PO), è il massimo consentito dalla specifica (qualsiasi altra cosa sarebbe specifica del fornitore.) Un po 'fuori dalla tangente, i programmatori dovrebbero (almeno nel caso generale) considerarlo come il loro limite superiore (a meno che non abbiano un motivo valido per fare affidamento su un limite superiore specifico del fornitore) ... io penso.
luis.espinal,

5
In un'altra nota, inizierei a tagliarmi se dovessi lavorare con un codice che aveva catene di dereferenziamento a lungo culo (specialmente quando
liberato ovunque in

11
@beryllium: di solito questi numeri provengono da un sondaggio sul software di pre-standardizzazione. In questo caso presumibilmente hanno esaminato i programmi C comuni e i compilatori C esistenti e hanno trovato almeno un compilatore che avrebbe problemi con più di 12 e / o nessun programma che si sarebbe rotto se lo avessi limitato a 12.

155

In realtà, i programmi C utilizzano comunemente l'indirizzamento infinito del puntatore. Uno o due livelli statici sono comuni. La tripla indiretta è rara. Ma infinito è molto comune.

L'indirizzamento del puntatore infinito si ottiene con l'aiuto di una struttura, ovviamente, non con un dichiaratore diretto, il che sarebbe impossibile. È necessaria una struttura per poter includere altri dati in questa struttura ai diversi livelli in cui ciò può terminare.

struct list { struct list *next; ... };

ora puoi avere list->next->next->next->...->next. Questo è in realtà solo più indirections puntatore: *(*(..(*(*(*list).next).next).next...).next).next. E .nextfondamentalmente è un noop quando è il primo membro della struttura, quindi possiamo immaginarlo come ***..***ptr.

Non c'è davvero alcun limite su questo perché i collegamenti possono essere attraversati con un ciclo piuttosto che un'espressione gigante come questa, e inoltre, la struttura può essere facilmente resa circolare.

Quindi, in altre parole, gli elenchi collegati possono essere l'esempio finale dell'aggiunta di un altro livello di riferimento indiretto per risolvere un problema, dal momento che lo stai facendo in modo dinamico con ogni operazione push. :)


48
Questo è un problema completamente diverso, tuttavia: una struttura che contiene un puntatore a un'altra struttura è molto diversa da un puntatore-puntatore. Un int ***** è un tipo distinto da un int ****.
soffice

12
Non è "molto" diverso. La differenza è soffice. È più vicino alla sintassi che alla semantica. Un puntatore a un oggetto puntatore o un puntatore a un oggetto struttura che contiene un puntatore? È lo stesso tipo di cose. Arrivare al decimo elemento di un elenco sono dieci livelli di indirizzamento indiretto. (Naturalmente, la capacità di esprimere una struttura infinita dipende dal fatto che il tipo di struttura sia in grado di puntare a se stesso tramite il tipo di struttura incompleta in modo che sia list->nexte list->next->nextlo stesso tipo; altrimenti dovremmo costruire un tipo infinito.)
Kaz

34
Non ho notato consapevolmente che il tuo nome è soffice quando ho usato la parola "soffice". Influenza inconscia? Ma sono sicuro di aver usato la parola in questo modo prima.
Kaz,

3
Ricorda anche che nel linguaggio macchina, potresti semplicemente iterare su qualcosa come LOAD R1, [R1]purché R1 sia un puntatore valido ad ogni passo. Non ci sono tipi diversi da "parola che contiene un indirizzo". La presenza o meno di tipi dichiarati non determina la direzione indiretta e il numero di livelli che ha.
Kaz,

4
Non se la struttura è circolare. Se R1contiene l'indirizzo di una posizione che punta a se stesso, LOAD R1, [R1]può essere eseguito in un ciclo infinito.
Kaz,

83

teoricamente:

Puoi avere tutti i livelli di riferimenti indiretti che desideri.

In pratica:

Naturalmente, nulla che consuma memoria può essere indefinito, ci saranno limitazioni dovute alle risorse disponibili nell'ambiente host. Quindi praticamente esiste un limite massimo a ciò che un'implementazione può supportare e l'implementazione deve documentarla in modo appropriato. Pertanto, in tutti questi artefatti, lo standard non specifica il limite massimo, ma specifica i limiti inferiori.

Ecco il riferimento:

Standard C99 5.2.4.1 Limiti di traduzione:

- 12 dichiaratori di puntatore, matrice e funzione (in qualsiasi combinazione) che modificano un tipo aritmetico, di struttura, di unione o di vuoto in una dichiarazione.

Questo specifica il limite inferiore che ogni implementazione deve supportare. Si noti che in una nota a piè di pagina lo standard dice inoltre:

18) Le implementazioni dovrebbero evitare di imporre limiti di traduzione fissi quando possibile.


16
le indirette non traboccano di pile!
Basile Starynkevitch il

1
Corretto, ho avuto questa sensazione errie di leggere e rispondere al q come limite di parametri passati alla funzione. Non so perché ?!
Risparmia il

2
@basile - Mi aspetto che lo stack-depth sia un problema nel parser. Molti algoritmi di analisi formale hanno uno stack come componente chiave. La maggior parte dei compilatori C ++ probabilmente usa una variante di discesa ricorsiva, ma anche quella si basa sullo stack del processore (o, pedanticamente, sul linguaggio che agisce come se ci fosse uno stack del processore). Più nidificazione delle regole grammaticali significa uno stack più profondo.
Steve314,

8
le indirette non traboccano di pile! -> No! lo stack del parser può traboccare. In che modo lo stack è correlato all'indirizzamento del puntatore? Parser stack!
Pavan Manjunath l'

Se *è sovraccarico per un numero di classi in una riga e ogni sovraccarico restituisce un oggetto di altro tipo nella riga, allora può esserci uno stackoverflow per tali chiamate di funzione concatenate.
Nawaz,

76

Come hanno detto le persone, nessun limite "in teoria". Tuttavia, per interesse, ho eseguito questo con g ++ 4.1.2 e ha funzionato con dimensioni fino a 20.000. La compilazione è stata piuttosto lenta, quindi non ho provato più in alto. Quindi immagino che g ++ non imponga alcun limite. (Prova a impostare size = 10e guardare in ptr.cpp se non è immediatamente ovvio.)

g++ create.cpp -o create ; ./create > ptr.cpp ; g++ ptr.cpp -o ptr ; ./ptr

create.cpp

#include <iostream>

int main()
{
    const int size = 200;
    std::cout << "#include <iostream>\n\n";
    std::cout << "int main()\n{\n";
    std::cout << "    int i0 = " << size << ";";
    for (int i = 1; i < size; ++i)
    {
        std::cout << "    int ";
        for (int j = 0; j < i; ++j) std::cout << "*";
        std::cout << " i" << i << " = &i" << i-1 << ";\n";
    }
    std::cout << "    std::cout << ";
    for (int i = 1; i < size; ++i) std::cout << "*";
    std::cout << "i" << size-1 << " << \"\\n\";\n";
    std::cout << "    return 0;\n}\n";
    return 0;
}

72
Non ho potuto ottenere più di 98242 quando l'ho provato. (Ho fatto lo script in Python, raddoppiando il numero di *fino a quando non ne ho ottenuto uno che non ha funzionato, e quello precedente che è passato; ho quindi fatto una ricerca binaria in quell'intervallo per il primo che ha fallito. L'intero test ha richiesto meno di un secondo per correre.)
James Kanze,

63

Sembra divertente da controllare.

  • Visual Studio 2010 (su Windows 7), è possibile avere 1011 livelli prima di ottenere questo errore:

    errore irreversibile C1026: overflow dello stack del parser, programma troppo complesso

  • gcc (Ubuntu), 100k + *senza crash! Immagino che l'hardware sia il limite qui.

(testato solo con una dichiarazione variabile)


5
In effetti, le produzioni per gli operatori unari sono ricorsive a destra, il che significa che un parser di riduzione-spostamento sposta tutti i *nodi nello stack prima di poter effettuare una riduzione.
Kaz,

29

Non ci sono limiti, controlla l'esempio qui .

La risposta dipende da cosa intendi per "livelli di puntatori". Se intendi "Quanti livelli di indiretta puoi avere in una singola dichiarazione?" la risposta è "Almeno 12."

int i = 0;

int *ip01 = & i;

int **ip02 = & ip01;

int ***ip03 = & ip02;

int ****ip04 = & ip03;

int *****ip05 = & ip04;

int ******ip06 = & ip05;

int *******ip07 = & ip06;

int ********ip08 = & ip07;

int *********ip09 = & ip08;

int **********ip10 = & ip09;

int ***********ip11 = & ip10;

int ************ip12 = & ip11;

************ip12 = 1; /* i = 1 */

Se intendi "Quanti livelli di puntatore puoi usare prima che il programma diventi difficile da leggere", è una questione di gusti, ma c'è un limite. Avere due livelli di indiretta (un puntatore a un puntatore a qualcosa) è comune. Nient'altro diventa più difficile pensare facilmente; non farlo a meno che l'alternativa sarebbe peggio.

Se intendi "Quanti livelli di indiretta del puntatore puoi avere in fase di esecuzione", non ci sono limiti. Questo punto è particolarmente importante per gli elenchi circolari, in cui ciascun nodo punta al successivo. Il tuo programma può seguire i puntatori per sempre.


7
C'è quasi certamente un limite, dal momento che il compilatore deve tenere traccia delle informazioni in una quantità finita di memoria. (si g++interrompe con un errore interno a 98242 sulla mia macchina. Mi aspetto che il limite effettivo dipenda dalla macchina e dal carico. Inoltre, non mi aspetto che questo sia un problema nel codice reale.)
James Kanze,

2
Sì @MatthieuM. : Ho appena considerato teoricamente :) Grazie James per aver completato la risposta
Nandkumar Tekale,

3
Bene, le liste collegate non sono in realtà un puntatore a un puntatore, sono un puntatore a una struttura che contiene un puntatore (o quello o
finisci

1
@ Random832: Nand ha detto 'Se intendi "Quanti livelli di indiretta dei puntatori puoi avere in fase di esecuzione", quindi rimuoveva esplicitamente la restrizione di parlare solo di puntatori a puntatori (* n).
LarsH

1
Non capisco il punto: ' Non c'è limite, controlla l'esempio qui. 'L'esempio non è la prova che non ci sono limiti. Dimostra solo che è possibile una indiretta a 12 stelle. Né dimostra nulla circ_listdell'esempio riguardante la domanda del PO: il fatto che sia possibile attraversare un elenco di puntatori non implica che il compilatore possa compilare una indiretta n-stelle.
Alberto,

24

In realtà è ancora più divertente con il puntatore alle funzioni.

#include <cstdio>

typedef void (*FuncType)();

static void Print() { std::printf("%s", "Hello, World!\n"); }

int main() {
  FuncType const ft = &Print;
  ft();
  (*ft)();
  (**ft)();
  /* ... */
}

Come illustrato qui, questo dà:

Ciao mondo!
Ciao mondo!
Ciao mondo!

E non comporta alcun sovraccarico di runtime, quindi puoi probabilmente impilarli quanto vuoi ... fino a quando il compilatore non soffoca sul file.


21

Non c'è limite . Un puntatore è un pezzo di memoria il cui contenuto è un indirizzo.
Come hai detto

int a = 10;
int *p = &a;

Un puntatore a un puntatore è anche una variabile che contiene un indirizzo di un altro puntatore.

int **q = &p;

Qui qè puntatore a puntatore pche contiene l'indirizzo di cui già contiene l'indirizzo di a.

Non c'è nulla di particolarmente speciale in un puntatore a un puntatore.
Quindi non vi è alcun limite alla catena di pon pon che tengono l'indirizzo di un altro puntatore.
vale a dire.

 int **************************************************************************z;

È permesso.


18

Ogni sviluppatore C ++ dovrebbe aver sentito parlare del (in) famoso programmatore a tre stelle

E sembra che ci sia davvero una "barriera puntatore" magica che deve essere mimetizzata

Citazione da C2:

Programmatore a tre stelle

Un sistema di classificazione per programmatori C. Più indiretti sono i tuoi puntatori (cioè più "*" prima delle tue variabili), maggiore sarà la tua reputazione. I programmatori C no-star sono praticamente inesistenti, poiché praticamente tutti i programmi non banali richiedono l'uso di puntatori. La maggior parte sono programmatori a una stella. Ai vecchi tempi (beh, sono giovane, quindi almeno per me sembrano dei vecchi tempi), a volte si trova un pezzo di codice fatto da un programmatore a tre stelle e un brivido di soggezione. Alcune persone hanno persino affermato di aver visto un codice a tre stelle con puntatori di funzione coinvolti, a più di un livello di indiretta. A me sembrava reale quanto gli UFO.


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… e simili è stato scritto da un programmatore a 4 stelle. È anche un mio amico e, se leggi abbastanza il codice, capirai il motivo per cui è degno di 4 stelle.
Jeff,

13

Si noti che qui ci sono due possibili domande: quanti livelli di indiretta del puntatore possiamo raggiungere in un tipo C e quanti livelli di indiretta del puntatore possiamo inserire in un singolo dichiaratore.

Lo standard C consente di imporre un massimo al primo (e fornisce un valore minimo per quello). Ma ciò può essere eluso tramite più dichiarazioni typedef:

typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */

Quindi, in definitiva, si tratta di un problema di implementazione collegato all'idea di quanto grande / complesso possa essere realizzato un programma C prima di essere rifiutato, che è molto specifico del compilatore.


4

Vorrei sottolineare che la produzione di un tipo con un numero arbitrario di * è qualcosa che può accadere con la metaprogrammazione dei template. Dimentico esattamente quello che stavo facendo, ma mi è stato suggerito di poter produrre nuovi tipi distinti che hanno una sorta di meta-manovra tra di loro usando i tipi ricorsivi T *.

La metaprogrammazione dei template è una lenta discesa nella follia, quindi non è necessario inventare scuse quando si genera un tipo con diverse migliaia di livelli di riferimento indiretto. È solo un modo pratico per mappare numeri interi peano, ad esempio, sull'espansione del modello come linguaggio funzionale.


Ammetto di non comprendere appieno la tua risposta, ma mi ha dato una nuova area da esplorare. :)
ankush981

3

La Regola 17.5 della norma MISRA C del 2004 vieta più di 2 livelli di indiretta del puntatore.


15
Abbastanza sicuro che sia una raccomandazione per i programmatori, non per i compilatori.
Cole Johnson,

3
Ho letto il documento con la regola 17.5 su più di 2 livelli di indiretta del puntatore. E non proibisce necessariamente più di 2 livelli. Afferma che la sentenza dovrebbe essere seguita in quanto più di 2 livelli sono "non-compliant"conformi ai loro standard. La parola o frase importante nella loro sentenza è l'uso della parola "should"da questa affermazione: Use of more than 2 levels of indirection can seriously impair the ability to understand the behavior of the code, and should therefore be avoided.queste sono le linee guida stabilite da questa organizzazione in contrapposizione alle regole stabilite dallo standard linguistico.
Francis Cugler,

1

Non esiste un limite reale, ma esiste un limite. Tutti i puntatori sono variabili che di solito vengono archiviate nello stack e non nell'heap . Lo stack è generalmente piccolo (è possibile modificarne le dimensioni durante alcuni collegamenti). Supponiamo quindi che tu abbia uno stack da 4 MB, di dimensioni abbastanza normali. E supponiamo di avere un puntatore che ha una dimensione di 4 byte (le dimensioni del puntatore non sono le stesse a seconda delle impostazioni di architettura, destinazione e compilatore).

In questo caso il 4 MB / 4 b = 1024numero massimo possibile sarebbe 1048576, ma non dovremmo ignorare il fatto che alcune altre cose sono in pila.

Tuttavia, alcuni compilatori possono avere il numero massimo di catene di puntatori, ma il limite è la dimensione dello stack. Quindi se aumenti le dimensioni dello stack durante il collegamento con infinito e disponi di una macchina con memoria infinita che esegue un sistema operativo che gestisce tale memoria in modo da avere una catena di puntatori illimitata.

Se usi int *ptr = new int;e metti il ​​tuo puntatore nell'heap, questo non è un modo usuale di limitare la dimensione dell'heap, non lo stack.

EDIT Basta rendersene conto infinity / 2 = infinity. Se la macchina ha più memoria, aumenta la dimensione del puntatore. Quindi se la memoria è infinita e la dimensione del puntatore è infinito, allora è una cattiva notizia ... :)


4
A) I puntatori possono essere memorizzati sull'heap ( new int*). B) An int*e an int**********hanno le stesse dimensioni, almeno su architetture ragionevoli.

@rightfold A) Sì, i puntatori possono essere memorizzati nell'heap. Ma sarebbe molto diverso come creare un contenitore che contiene puntatori che indicano il puntatore precedente successivo. B) Ovviamente int*e uno int**********ha le stesse dimensioni, non ho detto che hanno differenti.
ST3,

2
Quindi non vedo come la dimensione dello stack sia anche lontanamente rilevante.

@rightfold Ho pensato al solito modo di distribuzione dei dati quando tutti i dati sono in heap e nello stack sono solo dei puntatori a quei dati. Sarebbe il solito modo, ma sono d'accordo che è possibile mettere i puntatori in pila.
ST3

"Naturalmente int * e un int ********** hanno le stesse dimensioni" - lo standard non lo garantisce (anche se non conosco piattaforme dove non è vero).
Martin Bonner supporta Monica il

0

Dipende dal luogo in cui vengono archiviati i puntatori. Se sono in pila hai un limite abbastanza basso . Se lo memorizzi in heap, il limite è molto più elevato.

Guarda questo programma:

#include <iostream>

const int CBlockSize = 1048576;

int main() 
{
    int number = 0;
    int** ptr = new int*[CBlockSize];

    ptr[0] = &number;

    for (int i = 1; i < CBlockSize; ++i)
        ptr[i] = reinterpret_cast<int *> (&ptr[i - 1]);

    for (int i = CBlockSize-1; i >= 0; --i)
        std::cout << i << " " << (int)ptr[i] << "->" << *ptr[i] << std::endl;

    return 0;
}

Crea puntatori 1M e agli spettacoli cosa punta a ciò che è facile notare ciò che la catena va alla prima variabile number.

BTW. Utilizza la 92KRAM, quindi immagina quanto in profondità puoi andare.

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.