Wie viele Zeigerebenen können wir haben?


443

Wie viele Zeiger (* ) sind in einer einzelnen Variablen zulässig?

Betrachten wir das folgende Beispiel.

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

Ebenso können wir haben

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

und so weiter.

Zum Beispiel,

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

582
Wenn das jemals zu einem echten Problem für Sie wird, machen Sie etwas sehr Falsches.
ThiefMaster

279
Sie können so lange Zeigerebenen hinzufügen, bis Ihr Gehirn explodiert oder der Compiler schmilzt - je nachdem, was am schnellsten passiert.
JeremyP

47
Da ein Zeiger auf einen Zeiger wieder nur ein Zeiger ist, sollte es keine theoretische Grenze geben. Vielleicht wird der Compiler nicht in der Lage sein, über eine lächerlich hohe Grenze hinaus damit umzugehen, aber gut ...
Christian Rau

73
mit dem neuesten c ++ solltest du so etwas wie verwendenstd::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx

44
@josefx - dies zeigt ein Problem im C ++ - Standard - es gibt keine Möglichkeit, intelligente Zeiger auf Potenzen zu setzen. Wir müssen sofort eine Erweiterung fordern, um z. B. (pow (std::shared_ptr, -0.3))<T> x;-0,3 Indirektionsebenen zu unterstützen.
Steve314

Antworten:


400

Der CStandard legt die Untergrenze fest:

5.2.4.1 Übersetzungsgrenzen

276 Die Implementierung muss in der Lage sein, mindestens ein Programm zu übersetzen und auszuführen, das mindestens eine Instanz jeder der folgenden Grenzen enthält: [...]

279 - 12 Zeiger-, Array- und Funktionsdeklaratoren (in beliebigen Kombinationen), die einen Arithmetik-, Struktur-, Vereinigungs- oder Leertyp in einer Deklaration ändern

Die Obergrenze ist implementierungsspezifisch.


121
Der C ++ - Standard "empfiehlt", dass eine Implementierung mindestens 256 unterstützt. (Lesbarkeit empfiehlt, dass Sie 2 oder 3 nicht überschreiten, und selbst dann: mehr als eine sollte außergewöhnlich sein.)
James Kanze

22
Bei dieser Grenze geht es darum, wie viele in einer einzelnen Erklärung enthalten sind. Es gibt keine Obergrenze für die Indirektion, die Sie über mehrere typedefs erreichen können.
Kaz

11
@ Kaz - ja, das stimmt. Aber da die Spezifikation ist (kein Wortspiel beabsichtigt) eine verbindliche Untergrenze spezifiziert, ist es die effektive Obergrenze all spec-konformen Compilern sind erforderlich , um Unterstützung. Es könnte natürlich niedriger sein als die herstellerspezifische Obergrenze. Anders formuliert (um es an der Frage des OP auszurichten), ist es das Maximum, das von der Spezifikation zugelassen wird (alles andere wäre herstellerspezifisch). Programmierer sollten dies (zumindest im allgemeinen Fall) ein wenig von der Tangente abweichen Obergrenze (es sei denn, sie haben einen gültigen Grund, sich auf eine herstellerspezifische Obergrenze zu verlassen) ... denke ich.
Luis.espinal

5
In einem anderen Punkt würde ich anfangen, mich selbst zu schneiden, wenn ich mit Code arbeiten müsste, der
langarschige

11
@beryllium: Normalerweise stammen diese Zahlen aus einer Umfrage zur Vorstandardisierungssoftware. In diesem Fall haben sie sich vermutlich gängige C-Programme und vorhandene C-Compiler angesehen und mindestens einen Compiler gefunden, der Probleme mit mehr als 12 hatte und / oder keine Programme, die kaputt gingen, wenn Sie ihn auf 12 beschränkten.

155

Tatsächlich verwenden C-Programme üblicherweise die Indirektion unendlicher Zeiger. Ein oder zwei statische Ebenen sind üblich. Dreifache Indirektion ist selten. Aber unendlich ist sehr verbreitet.

Eine unendliche Zeiger-Indirektion wird natürlich mit Hilfe einer Struktur erreicht, nicht mit einem direkten Deklarator, was unmöglich wäre. Und es wird eine Struktur benötigt, damit Sie andere Daten in diese Struktur auf den verschiedenen Ebenen aufnehmen können, auf denen dies enden kann.

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

jetzt kannst du haben list->next->next->next->...->next. Dies ist wirklich nur mehrere Zeiger Indirektionen : *(*(..(*(*(*list).next).next).next...).next).next. Und das .nextist im Grunde ein Noop, wenn es das erste Mitglied der Struktur ist, also können wir uns das als vorstellen ***..***ptr.

Dies ist wirklich unbegrenzt, da die Verknüpfungen eher mit einer Schleife als mit einem riesigen Ausdruck wie diesem durchlaufen werden können und außerdem die Struktur leicht kreisförmig gemacht werden kann.

Mit anderen Worten, verknüpfte Listen können das ultimative Beispiel für das Hinzufügen einer weiteren Indirektionsebene zur Lösung eines Problems sein, da Sie dies bei jeder Push-Operation dynamisch tun. :) :)


48
Das ist jedoch ein ganz anderes Problem - eine Struktur, die einen Zeiger auf eine andere Struktur enthält, unterscheidet sich stark von einem Zeiger-Zeiger. Ein int ***** ist ein anderer Typ als ein int ****.
flauschig

12
Es ist nicht "sehr" anders. Der Unterschied ist flauschig. Es ist näher an der Syntax als an der Semantik. Ein Zeiger auf ein Zeigerobjekt oder ein Zeiger auf ein Strukturobjekt, das einen Zeiger enthält? Es ist das Gleiche. Um zum zehnten Element einer Liste zu gelangen, gibt es zehn Ebenen für die Adressierung der Indirektion. (Natürlich hängt die Fähigkeit, eine unendliche Struktur auszudrücken, davon ab, dass der Strukturtyp über den unvollständigen Strukturtyp auf sich selbst verweisen kann, so dass list->nextund list->next->nextvom selben Typ sind; andernfalls müssten wir einen unendlichen Typ konstruieren.)
Kaz

34
Ich habe nicht bewusst bemerkt, dass Ihr Name flauschig ist, als ich das Wort "flauschig" verwendete. Unterbewusster Einfluss? Aber ich bin sicher, dass ich das Wort schon einmal so verwendet habe.
Kaz

3
Denken Sie auch daran, dass Sie in der Maschinensprache so etwas wie iterieren können, LOAD R1, [R1]solange R1 bei jedem Schritt ein gültiger Zeiger ist. Es gibt keine anderen Typen als "Wort, das eine Adresse enthält". Ob es deklarierte Typen gibt oder nicht, bestimmt nicht die Indirektion und die Anzahl der Ebenen.
Kaz

4
Nicht wenn die Struktur kreisförmig ist. Wenn R1die Adresse eines Ortes enthält, der auf sich selbst zeigt, LOAD R1, [R1]kann dies in einer Endlosschleife ausgeführt werden.
Kaz

83

Theoretisch:

Sie können so viele Indirektionsebenen haben, wie Sie möchten.

Praktisch:

Natürlich kann nichts, was Speicher verbraucht, unbegrenzt sein. Aufgrund der in der Hostumgebung verfügbaren Ressourcen gibt es Einschränkungen. In der Praxis gibt es also eine Höchstgrenze für die Unterstützung einer Implementierung, und die Implementierung muss diese angemessen dokumentieren. In all diesen Artefakten gibt der Standard nicht die maximale Grenze an, sondern die unteren Grenzen.

Hier ist die Referenz:

C99 Standard 5.2.4.1 Übersetzungsgrenzen:

- 12 Zeiger-, Array- und Funktionsdeklaratoren (in beliebigen Kombinationen), die einen Arithmetik-, Struktur-, Vereinigungs- oder Leertyp in einer Deklaration ändern.

Dies gibt die Untergrenze an, die jede Implementierung unterstützen muss . Beachten Sie, dass in einer Footenote der Standard weiter sagt:

18) Durch Implementierungen sollten nach Möglichkeit feste Übersetzungsgrenzen vermieden werden.


16
Indirektionen überlaufen keine Stapel!
Basile Starynkevitch

1
Korrigiert hatte ich das Gefühl, das q als Grenze der an die Funktion übergebenen Parameter zu lesen und zu beantworten. Ich weiß nicht warum?!
Alok Save

2
@basile - Ich würde erwarten, dass die Stapeltiefe ein Problem im Parser ist. Viele formale Parsing-Algorithmen haben einen Stapel als Schlüsselkomponente. Die meisten C ++ - Compiler verwenden wahrscheinlich eine Variante des rekursiven Abstiegs, aber selbst das hängt vom Prozessorstapel ab (oder pedantisch von der Sprache, die sich so verhält, als ob es einen Prozessorstapel gäbe). Mehr Verschachtelung von Grammatikregeln bedeutet einen tieferen Stapel.
Steve314

8
Indirektionen überlaufen keine Stapel! -> Nein! Parser-Stack kann überlaufen. In welcher Beziehung steht der Stapel zur Zeiger-Indirektion? Parser-Stapel!
Pavan Manjunath

Wenn *für eine Reihe von Klassen in einer Zeile überladen ist und jede Überladung ein Objekt eines anderen Typs in der Zeile zurückgibt, kann es für solche verketteten Funktionsaufrufe zu einem Stapelüberlauf kommen.
Nawaz

76

Wie die Leute gesagt haben, keine Grenze "in der Theorie". Aus Interesse habe ich dies jedoch mit g ++ 4.1.2 ausgeführt und es funktionierte mit einer Größe von bis zu 20.000. Das Kompilieren war allerdings ziemlich langsam, deshalb habe ich es nicht höher versucht. Ich würde also vermuten, dass g ++ auch keine Grenzen setzt. (Versuchen Sie, size = 10ptr.cpp einzustellen und zu suchen, wenn dies nicht sofort offensichtlich ist.)

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
Ich konnte nicht mehr als 98242 bekommen, als ich es versuchte. (Ich habe das Skript in Python erstellt und die Anzahl verdoppelt, *bis ich eines erhalten habe, das fehlgeschlagen ist, und das vorhergehende, das bestanden hat. Dann habe ich über dieses Intervall eine binäre Suche nach dem ersten durchgeführt, das fehlgeschlagen ist. Der gesamte Test dauerte weniger als eine Sekunde zu rennen.)
James Kanze

63

Klingt lustig zu überprüfen.

  • In Visual Studio 2010 (unter Windows 7) können Sie 1011 Ebenen haben, bevor dieser Fehler angezeigt wird:

    Schwerwiegender Fehler C1026: Parser-Stack-Überlauf, Programm zu komplex

  • gcc (Ubuntu), 100k + *ohne Absturz! Ich denke, die Hardware ist hier die Grenze.

(getestet mit nur einer Variablendeklaration)


5
In der Tat sind die Produktionen für unäre Operatoren rechtsrekursiv, was bedeutet, dass ein Parser mit reduzierter Verschiebung alle *Knoten auf den Stapel verschiebt, bevor eine Reduzierung vorgenommen werden kann.
Kaz

28

Es gibt keine Begrenzung, siehe Beispiel hier .

Die Antwort hängt davon ab, was Sie unter "Zeigerstufen" verstehen. Wenn Sie meinen "Wie viele Indirektionsebenen können Sie in einer einzelnen Deklaration haben?" Die Antwort lautet "Mindestens 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 */

Wenn Sie meinen "Wie viele Zeigerebenen können Sie verwenden, bevor das Programm schwer zu lesen ist", ist das Geschmackssache, aber es gibt eine Grenze. Es ist üblich, zwei Indirektionsebenen zu haben (einen Zeiger auf einen Zeiger auf etwas). Mehr als das wird etwas schwieriger, leicht darüber nachzudenken; Tu es nicht, es sei denn, die Alternative wäre schlimmer.

Wenn Sie "Wie viele Ebenen der Zeiger-Indirektion können Sie zur Laufzeit haben" meinen, gibt es keine Begrenzung. Dieser Punkt ist besonders wichtig für zirkuläre Listen, in denen jeder Knoten auf den nächsten zeigt. Ihr Programm kann den Zeigern für immer folgen.


7
Es gibt mit ziemlicher Sicherheit eine Grenze, da der Compiler die Informationen in einer begrenzten Menge an Speicher verfolgen muss. ( g++bricht mit einem internen Fehler bei 98242 auf meinem Computer ab. Ich gehe davon aus, dass das tatsächliche Limit von der Maschine und der Last abhängt. Ich erwarte auch nicht, dass dies ein Problem im realen Code ist.)
James Kanze

2
Ja @MatthieuM. : Ich habe nur theoretisch überlegt :) Danke James für die
vollständige

3
Nun, verknüpfte Listen sind nicht wirklich ein Zeiger auf einen Zeiger, sondern ein Zeiger auf eine Struktur, die einen Zeiger enthält (entweder das oder Sie machen am Ende viel unnötiges Casting)
Random832

1
@ Random832: Nand sagte: "Wenn Sie" Wie viele Ebenen der Zeiger-Indirektion können Sie zur Laufzeit haben? "Meinen, hob er explizit die Einschränkung auf, nur über Zeiger auf Zeiger zu sprechen (* n).
LarsH

1
Ich verstehe Ihren Standpunkt nicht: ' Es gibt keine Begrenzung, überprüfen Sie das Beispiel hier. 'Das Beispiel ist kein Beweis dafür, dass es keine Grenzen gibt. Es beweist nur, dass eine 12-Sterne-Indirektion möglich ist. Beides beweist nichts als circ_listBeispiel für die Frage des OP: Die Tatsache, dass Sie eine Zeigerliste durchlaufen können, bedeutet nicht, dass der Compiler eine Indirektion mit n Sternen kompilieren kann.
Alberto

24

Es ist sogar noch lustiger mit Zeiger auf Funktionen.

#include <cstdio>

typedef void (*FuncType)();

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

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

Wie hier dargestellt, ergibt dies:

Hallo Welt!
Hallo Welt!
Hallo Welt!

Und es ist kein Laufzeitaufwand erforderlich, sodass Sie sie wahrscheinlich so oft stapeln können, wie Sie möchten ... bis Ihr Compiler die Datei erstickt.


20

Es gibt keine Begrenzung . Ein Zeiger ist ein Speicherblock, dessen Inhalt eine Adresse ist.
Wie du gesagt hast

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

Ein Zeiger auf einen Zeiger ist auch eine Variable, die eine Adresse eines anderen Zeigers enthält.

int **q = &p;

Hier qist Zeiger auf Zeiger, dessen Adresse pbereits die Adresse von enthälta .

Ein Zeiger auf einen Zeiger hat nichts Besonderes.
Es gibt also keine Begrenzung für die Kette von Ponitern, die die Adresse eines anderen Zeigers enthalten.
dh.

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

ist erlaubt.


17

Jeder C ++ - Entwickler sollte von dem (in) berühmten Drei-Sterne-Programmierer gehört haben

Und es scheint wirklich eine magische "Zeigerbarriere" zu geben, die getarnt werden muss

Zitat aus C2:

Drei-Sterne-Programmierer

Ein Bewertungssystem für C-Programmierer. Je indirekter Ihre Zeiger sind (dh je mehr "*" vor Ihren Variablen steht), desto höher ist Ihr Ruf. No-Star-C-Programmierer sind praktisch nicht vorhanden, da praktisch alle nicht trivialen Programme die Verwendung von Zeigern erfordern. Die meisten sind Ein-Stern-Programmierer. In alten Zeiten (nun, ich bin jung, also sehen diese für mich zumindest wie alte Zeiten aus) fand man gelegentlich einen Code, der von einem Drei-Sterne-Programmierer erstellt wurde und vor Ehrfurcht zitterte. Einige Leute behaupteten sogar, sie hätten Drei-Sterne-Code mit Funktionszeigern auf mehr als einer Indirektionsebene gesehen. Für mich klang es so real wie UFOs.


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… und dergleichen wurde von einem 4-Sterne-Programmierer geschrieben. Er ist auch ein Freund von mir und wenn Sie den Code genug lesen, werden Sie den Grund verstehen, warum er 4 Sterne wert ist.
Jeff

13

Beachten Sie, dass es hier zwei mögliche Fragen gibt: Wie viele Ebenen der Zeiger-Indirektion können wir in einem C-Typ erreichen, und wie viele Ebenen der Zeiger-Indirektion können wir in einen einzelnen Deklarator einfügen.

Der C-Standard erlaubt es, dem ersteren ein Maximum aufzuerlegen (und gibt dafür einen minimalen Wert an). Dies kann jedoch über mehrere typedef-Deklarationen umgangen werden:

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

Letztendlich handelt es sich also um ein Implementierungsproblem, das mit der Idee zusammenhängt, wie groß / komplex ein C-Programm sein kann, bevor es abgelehnt wird, was sehr compilerspezifisch ist.


4

Ich möchte darauf hinweisen, dass das Erstellen eines Typs mit einer beliebigen Anzahl von * mit der Metaprogrammierung von Vorlagen möglich ist. Ich habe vergessen, was ich genau tat, aber es wurde vorgeschlagen, dass ich mithilfe rekursiver T * -Typen neue unterschiedliche Typen erzeugen könnte, zwischen denen eine Art Meta-Manöver besteht .

Template Metaprogramming ist ein langsamer Abstieg in den Wahnsinn, daher ist es nicht notwendig, Ausreden zu treffen, wenn ein Typ mit mehreren tausend Indirektionsebenen generiert wird. Es ist nur eine praktische Möglichkeit, Peano-Ganzzahlen beispielsweise der Vorlagenerweiterung als funktionale Sprache zuzuordnen.


Ich gebe zu, ich verstehe Ihre Antwort nicht vollständig, aber es gibt mir ein neues Gebiet, das ich erkunden kann. :)
ankush981

3

Regel 17.5 des MISRA C- Standards von 2004 verbietet mehr als zwei Ebenen der Zeiger-Indirektion.


15
Ich bin mir ziemlich sicher, dass dies eine Empfehlung für Programmierer ist, nicht für Compiler.
Cole Johnson

3
Ich habe das Dokument mit Regel 17.5 über mehr als zwei Ebenen der Zeiger-Indirektion gelesen. Und es verbietet nicht unbedingt mehr als 2 Level. Es heißt, dass das Urteil befolgt werden sollte, da mehr als 2 Ebenen "non-compliant"ihren Standards entsprechen. Das wichtige Wort oder der Satz in ihrer Entscheidung ist die Verwendung des Wortes "should"aus dieser Aussage: 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.Dies sind Richtlinien, die von dieser Organisation festgelegt wurden, im Gegensatz zu Regeln, die vom Sprachstandard festgelegt wurden.
Francis Cugler

1

Es gibt kein echtes Limit, aber es gibt ein Limit. Alle Zeiger sind Variablen, die normalerweise im Stapel und nicht im Heap gespeichert werden . Der Stapel ist normalerweise klein (es ist möglich, seine Größe während einer Verknüpfung zu ändern). Nehmen wir also an, Sie haben einen 4-MB-Stapel, was eine ganz normale Größe ist. Nehmen wir an, wir haben einen Zeiger mit einer Größe von 4 Bytes (die Zeigergrößen sind je nach Architektur, Ziel- und Compilereinstellungen nicht gleich).

In diesem Fall 4 MB / 4 b = 1024wäre die maximal mögliche Anzahl 1048576, aber wir sollten die Tatsache nicht ignorieren, dass einige andere Dinge im Stapel sind.

Einige Compiler haben möglicherweise die maximale Anzahl von Zeigerketten, aber die Grenze ist die Stapelgröße. Wenn Sie also die Stapelgröße während der Verknüpfung mit unendlich erhöhen und einen Computer mit unendlichem Speicher haben, auf dem das Betriebssystem ausgeführt wird, das diesen Speicher verarbeitet, haben Sie eine unbegrenzte Zeigerkette.

Wenn Sie int *ptr = new int;Ihren Zeiger verwenden und in den Heap setzen, ist dies nicht die übliche Beschränkung auf die Größe des Heapspeichers und nicht auf den Stapel.

BEARBEITEN Erkenne das einfach infinity / 2 = infinity. Wenn die Maschine mehr Speicher hat, erhöht sich die Zeigergröße. Wenn also der Speicher unendlich ist und die Größe des Zeigers unendlich ist, sind es schlechte Nachrichten ... :)


4
A) Zeiger können auf dem Heap gespeichert werden ( new int*). B) An int*und an int**********haben die gleiche Größe, zumindest auf vernünftigen Architekturen.

@rightfold A) Ja, Zeiger können im Heap gespeichert werden. Aber es wäre etwas ganz anderes, als einen Container zu erstellen, der Zeiger enthält, die auf den nächsten vorherigen Zeiger zeigen. B) Natürlich int*und int**********haben die gleiche Größe, ich habe nicht gesagt, dass sie unterschiedlich sind.
ST3

2
Dann sehe ich nicht, wie die Stapelgröße auch nur aus der Ferne relevant ist.

@rightfold Ich habe über die übliche Art der Datenverteilung nachgedacht, wenn sich alle Daten im Heap befinden und auf dem Stapel nur Zeiger auf diese Daten sind. Es wäre üblich , aber ich stimme zu, dass es möglich ist, Zeiger in Stapel zu setzen.
ST3

"Natürlich haben int * und ein int ********** die gleiche Größe" - der Standard garantiert dies nicht (obwohl ich keine Plattform kenne, auf der dies nicht der Fall ist).
Martin Bonner unterstützt Monica

0

Dies hängt von der Stelle ab, an der Sie Zeiger speichern. Wenn sie im Stapel sind, haben Sie ziemlich niedrige Grenze. Wenn Sie es auf einem Haufen speichern, ist Ihr Limit viel, viel höher.

Schauen Sie sich dieses Programm an:

#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;
}

Es werden 1M-Zeiger erstellt und auf den Shows, an welchem ​​Punkt zu erkennen ist, was die Kette zur ersten Variablen führt number.

Übrigens. Es wird 92KRAM verwendet, stellen Sie sich also vor, wie tief Sie gehen können.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.