Combien de niveaux de pointeurs pouvons-nous avoir?

444

Combien de pointeurs ( *) sont autorisés dans une seule variable?

Prenons l'exemple suivant.

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

De même, nous pouvons avoir

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

etc.

Par exemple,

int ****************zz;
Parag
la source
582
Si cela devient un réel problème pour vous, vous faites quelque chose de très mal.
ThiefMaster
279
Vous pouvez continuer à ajouter des niveaux de pointeurs jusqu'à ce que votre cerveau explose ou que le compilateur fond - selon ce qui se produit le plus tôt.
JeremyP
47
Puisqu'un pointeur vers un pointeur est à nouveau, eh bien, juste un pointeur, il ne devrait pas y avoir de limite théorique. Peut-être que le compilateur ne pourra pas le gérer au-delà d'une limite ridiculement élevée, mais bon ...
Christian Rau
73
avec le plus récent c ++, vous devriez utiliser quelque chose commestd::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx
44
@josefx - cela montre un problème dans la norme C ++ - il n'y a aucun moyen d'élever des pointeurs intelligents à des pouvoirs. Nous devons immédiatement exiger une extension pour prendre en charge, par exemple, les (pow (std::shared_ptr, -0.3))<T> x;niveaux d'indirection de -0,3.
Steve314

Réponses:

400

La Cnorme spécifie la limite inférieure:

5.2.4.1 Limites de traduction

276 L'implémentation doit pouvoir traduire et exécuter au moins un programme contenant au moins une instance de chacune des limites suivantes: [...]

279 - 12 déclarants de pointeur, de tableau et de fonction (dans toutes les combinaisons) modifiant un type arithmétique, de structure, d'union ou de vide dans une déclaration

La limite supérieure est spécifique à l'implémentation.

PP
la source
121
Le standard C ++ "recommande" qu'une implémentation prenne en charge au moins 256. (La lisibilité recommande que vous ne dépassiez pas 2 ou 3, et même alors: plus d'un devrait être exceptionnel.)
James Kanze
22
Cette limite est à peu près combien dans une seule déclaration; il n'impose pas de limite supérieure sur la quantité d'indirection que vous pouvez obtenir via plusieurs typedefs.
Kaz
11
@Kaz - oui, c'est vrai. Mais puisque la spécification spécifie (sans jeu de mots) une limite inférieure obligatoire, c'est la limite supérieure efficace que tous les compilateurs conformes aux spécifications doivent prendre en charge. Bien sûr, il pourrait être inférieur à la limite supérieure spécifique au fournisseur. Reformulé différemment (pour l'aligner sur la question de l'OP), c'est le maximum autorisé par la spécification (tout le reste serait spécifique au fournisseur). Un peu à l'écart de la tangente, les programmeurs devraient (au moins dans le cas général) traiter cela comme leur limite supérieure (à moins qu'ils aient une raison valable de s'appuyer sur une limite supérieure spécifique au fournisseur) ... me semble.
luis.espinal
5
Sur une autre note, je commencerais à me couper si je devais travailler avec du code qui avait des chaînes de déréférencement à long cul (surtout quand il était généreusement poivré partout ).
luis.espinal
11
@beryllium: Ces chiffres proviennent généralement d'une enquête sur les logiciels de pré-standardisation. Dans ce cas, ils ont vraisemblablement examiné les programmes C courants et les compilateurs C existants, et ont trouvé au moins un compilateur qui aurait des problèmes avec plus de 12 et / ou aucun programme qui ne fonctionnait pas si vous le restreigniez à 12.
155

En fait, les programmes C utilisent couramment l'indirection de pointeur infinie. Un ou deux niveaux statiques sont courants. La triple indirection est rare. Mais l'infini est très courant.

L'indirection de pointeur infini est obtenue à l'aide d'une structure, bien sûr, pas avec un déclarant direct, ce qui serait impossible. Et une structure est nécessaire pour que vous puissiez inclure d'autres données dans cette structure aux différents niveaux où cela peut se terminer.

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

maintenant vous pouvez avoir list->next->next->next->...->next. Ceci est vraiment juste plusieurs indirections pointeur: *(*(..(*(*(*list).next).next).next...).next).next. Et .nextc'est fondamentalement un noop quand c'est le premier membre de la structure, donc nous pouvons imaginer cela comme ***..***ptr.

Il n'y a vraiment aucune limite à cela car les liens peuvent être parcourus avec une boucle plutôt qu'avec une expression géante comme celle-ci, et de plus, la structure peut facilement être rendue circulaire.

Ainsi, en d'autres termes, les listes chaînées peuvent être l'exemple ultime de l'ajout d'un autre niveau d'indirection pour résoudre un problème, car vous le faites dynamiquement à chaque opération push. :)

Kaz
la source
48
C'est un problème complètement différent, cependant - une structure contenant un pointeur vers une autre structure est très différente d'un pointeur-pointeur. Un int ***** est un type distinct d'un int ****.
moelleux
12
Ce n'est pas "très" différent. La différence est moelleuse. Elle est plus proche de la syntaxe que de la sémantique. Un pointeur vers un objet pointeur, ou un pointeur vers un objet de structure qui contient un pointeur? C'est le même genre de chose. Pour atteindre le dixième élément d'une liste, il existe dix niveaux d'adressage indirect. (Bien sûr, la capacité à exprimer une structure infinie dépend du type de structure pouvant se pointer vers lui-même via le type de structure incomplète de sorte que list->nextet list->next->nextsont le même type; sinon nous aurions à construire un type infini.)
Kaz
34
Je n'ai pas consciemment remarqué que votre nom est moelleux lorsque j'ai utilisé le mot "moelleux". Influence subconsciente? Mais je suis sûr d'avoir déjà utilisé le mot de cette façon.
Kaz
3
Souvenez-vous également qu'en langage machine, vous pouvez simplement itérer sur quelque chose comme LOAD R1, [R1]aussi longtemps que R1 est un pointeur valide à chaque étape. Il n'y a aucun type impliqué autre que "mot qui contient une adresse". La présence ou non de types déclarés ne détermine pas l'indirection et le nombre de niveaux qu'elle possède.
Kaz
4
Pas si la structure est circulaire. Si R1contient l'adresse d'un emplacement qui pointe vers lui-même, il LOAD R1, [R1]peut être exécuté dans une boucle infinie.
Kaz
83

Théoriquement:

Vous pouvez avoir autant de niveaux d'indirections que vous le souhaitez.

Pratiquement:

Bien sûr, rien qui consomme de la mémoire ne peut être indéfini, il y aura des limitations en raison des ressources disponibles sur l'environnement hôte. Il existe donc pratiquement une limite maximale à ce qu'une implémentation peut prendre en charge et l'implémentation doit la documenter de manière appropriée. Ainsi, dans tous ces artefacts, la norme ne spécifie pas la limite maximale, mais elle spécifie les limites inférieures.

Voici la référence:

C99 Standard 5.2.4.1 Limites de traduction:

- 12 déclarants de pointeur, de tableau et de fonction (dans toutes les combinaisons) modifiant un type arithmétique, de structure, d'union ou de void dans une déclaration.

Cela spécifie la limite inférieure que chaque implémentation doit prendre en charge. Notez que dans une note de bas de page, la norme indique en outre:

18) Les mises en œuvre devraient éviter d'imposer des limites de traduction fixes dans la mesure du possible.

Alok Save
la source
16
les indirections ne débordent pas de piles!
Basile Starynkevitch
1
Corrigé, j'avais cette sensation de lecture et de réponse au q comme limite des paramètres transmis à la fonction. Je ne sais pas pourquoi?!
Alok Save
2
@basile - Je m'attends à ce que la profondeur de pile soit un problème dans l'analyseur. De nombreux algorithmes d'analyse formelle ont une pile comme composant clé. La plupart des compilateurs C ++ utilisent probablement une variante de descente récursive, mais même cela repose sur la pile du processeur (ou, pédantiquement, sur le langage agissant comme s'il y avait une pile du processeur). Plus d'imbrication des règles de grammaire signifie une pile plus profonde.
Steve314
8
les indirections ne débordent pas de piles! -> Non! la pile de l'analyseur peut déborder. Comment la pile est-elle liée à l'indirection du pointeur? Pile d'analyseur!
Pavan Manjunath
Si *est surchargé pour un certain nombre de classes dans une ligne et que chaque surcharge renvoie un objet d'un autre type dans la ligne, il peut y avoir stackoverflow pour de tels appels de fonction chaînés.
Nawaz
76

Comme on l'a dit, aucune limite "en théorie". Cependant, par intérêt, j'ai exécuté cela avec g ++ 4.1.2, et cela a fonctionné avec une taille allant jusqu'à 20 000. La compilation était cependant assez lente, donc je n'ai pas essayé plus haut. Donc je suppose que g ++ n'impose aucune limite non plus. (Essayez de définir size = 10et de rechercher dans ptr.cpp si ce n'est pas immédiatement évident.)

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;
}
BoBTFish
la source
72
Je n'ai pas pu obtenir plus de 98242 lorsque je l'ai essayé. (J'ai fait le script en Python, doublant le nombre de *jusqu'à ce que j'obtienne celui qui a échoué, et le précédent qui a réussi; j'ai ensuite fait une recherche binaire sur cet intervalle pour le premier qui a échoué. L'ensemble du test a pris moins d'une seconde à courir.)
James Kanze
63

Cela semble amusant à vérifier.

  • Visual Studio 2010 (sur Windows 7), vous pouvez avoir 1011 niveaux avant d'obtenir cette erreur:

    erreur fatale C1026: débordement de pile d'analyseur, programme trop complexe

  • gcc (Ubuntu), 100k + *sans plantage! Je suppose que le matériel est la limite ici.

(testé avec juste une déclaration de variable)

mihai
la source
5
En effet, les productions pour les opérateurs unaires sont récursives à droite, ce qui signifie qu'un analyseur de réduction-décalage décalera tous les *nœuds sur la pile avant de pouvoir effectuer une réduction.
Kaz
29

Il n'y a pas de limite, consultez l'exemple ici .

La réponse dépend de ce que vous entendez par «niveaux de pointeurs». Si vous voulez dire "Combien de niveaux d'indirection pouvez-vous avoir dans une seule déclaration?" la réponse est "au moins 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 */

Si vous voulez dire «combien de niveaux de pointeur pouvez-vous utiliser avant que le programme ne soit difficile à lire», c'est une question de goût, mais il y a une limite. Il est courant d'avoir deux niveaux d'indirection (un pointeur vers un pointeur vers quelque chose). Plus que cela devient un peu plus difficile à penser facilement; ne le faites pas à moins que l'alternative ne soit pire.

Si vous voulez dire «combien de niveaux d'indirection de pointeur pouvez-vous avoir à l'exécution», il n'y a pas de limite. Ce point est particulièrement important pour les listes circulaires, dans lesquelles chaque nœud pointe vers le suivant. Votre programme peut suivre les pointeurs pour toujours.

Nandkumar Tekale
la source
7
Il y a presque certainement une limite, car le compilateur doit garder la trace des informations dans une quantité limitée de mémoire. ( g++abandonne avec une erreur interne à 98242 sur ma machine. Je m'attends à ce que la limite réelle dépende de la machine et de la charge. Je ne m'attends pas non plus à ce que ce soit un problème dans le code réel.)
James Kanze
2
Ouais @MatthieuM. : Je viens de considérer théoriquement :) Merci James d'avoir répondu
Nandkumar Tekale
3
Eh bien, les listes liées ne sont pas vraiment un pointeur vers un pointeur, elles sont un pointeur vers une structure qui contient un pointeur (soit cela, soit vous
finissez
1
@ Random832: Nand a dit: «Si vous voulez dire« combien de niveaux d'indirection de pointeur pouvez-vous avoir au moment de l'exécution », alors il supprimait explicitement la restriction de ne parler que de pointeurs vers des pointeurs (* n).
LarsH
1
Je ne comprends pas votre point: « Il n'y a pas de limite, vérifiez l'exemple ici. «L'exemple n'est pas la preuve qu'il n'y a pas de limite. Cela prouve seulement qu'une indirection 12 étoiles est possible. Ni l'un ni l'autre ne prouve quoi que ce soit circ_listconcernant la question de l'OP: le fait que vous puissiez parcourir une liste de pointeurs n'implique pas que le compilateur puisse compiler une indirection à n étoiles.
Alberto
24

C'est en fait encore plus drôle avec un pointeur sur les fonctions.

#include <cstdio>

typedef void (*FuncType)();

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

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

Comme illustré ici, cela donne:

Bonjour le monde!
Bonjour le monde!
Bonjour le monde!

Et cela n'implique aucune surcharge d'exécution, vous pouvez donc probablement les empiler autant que vous le souhaitez ... jusqu'à ce que votre compilateur s'étouffe sur le fichier.

Matthieu M.
la source
21

Il n'y a aucune limite . Un pointeur est un morceau de mémoire dont le contenu est une adresse.
Comme tu dis

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

Un pointeur vers un pointeur est également une variable qui contient l'adresse d'un autre pointeur.

int **q = &p;

Voici un qpointeur à un pointeur contenant l’adresse pqui contient déjà l’adresse de a.

Il n'y a rien de particulièrement spécial à propos d'un pointeur sur un pointeur.
Il n'y a donc aucune limite sur la chaîne de poniters qui contiennent l'adresse d'un autre pointeur.
c'est à dire.

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

est autorisée.

Sachin Mhetre
la source
18

Chaque développeur C ++ aurait dû entendre parler du (in) célèbre programmeur trois étoiles

Et il semble vraiment y avoir une "barrière de pointeur" magique qui doit être camouflée

Citation de C2:

Programmeur trois étoiles

Un système de notation pour les programmeurs C. Plus vos pointeurs sont indirects (c'est-à-dire plus "*" devant vos variables), plus votre réputation sera élevée. Les programmeurs C sans étoiles sont pratiquement inexistants, car pratiquement tous les programmes non triviaux nécessitent l'utilisation de pointeurs. La plupart sont des programmeurs une étoile. Dans les temps anciens (enfin, je suis jeune, donc ceux-ci ressemblent à des temps anciens pour moi au moins), on trouvait parfois un morceau de code fait par un programmeur trois étoiles et frissonnait de crainte. Certaines personnes ont même affirmé avoir vu du code à trois étoiles avec des pointeurs de fonction impliqués, sur plus d'un niveau d'indirection. Cela sonnait aussi réel que des ovnis pour moi.

Mare Infinitus
la source
2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… et similaires ont été écrits par un programmeur 4 étoiles. C'est aussi un de mes amis et, si vous lisez suffisamment le code, vous comprendrez pourquoi il mérite 4 étoiles.
Jeff
13

Notez qu'il y a deux questions possibles ici: combien de niveaux d'indirection de pointeur nous pouvons atteindre dans un type C, et combien de niveaux d'indirection de pointeur nous pouvons remplir dans un seul déclarant.

La norme C permet d'imposer un maximum aux premiers (et donne une valeur minimale pour cela). Mais cela peut être contourné via plusieurs déclarations typedef:

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

En fin de compte, il s'agit d'un problème d'implémentation lié à l'idée de la taille / complexité d'un programme C avant qu'il ne soit rejeté, ce qui est très spécifique au compilateur.

Kaz
la source
4

Je voudrais souligner que la production d'un type avec un nombre arbitraire de * est quelque chose qui peut arriver avec la métaprogrammation de modèle. J'oublie ce que je faisais exactement, mais il a été suggéré que je puisse produire de nouveaux types distincts qui ont une sorte de méta-manoeuvre entre eux en utilisant des types T * récursifs .

La métaprogrammation de modèles est une lente descente dans la folie, il n'est donc pas nécessaire de faire des excuses lors de la génération d'un type avec plusieurs milliers de niveaux d'indirection. C'est juste un moyen pratique de mapper des entiers peano, par exemple, sur l'extension de modèle en tant que langage fonctionnel.

JDługosz
la source
J'avoue ne pas bien comprendre votre réponse, mais cela m'a donné un nouveau domaine à explorer. :)
ankush981
3

La règle 17.5 de la norme MISRA C 2004 interdit plus de 2 niveaux d'indirection de pointeur.

kostmo
la source
15
Je suis presque sûr que c'est une recommandation pour les programmeurs, pas pour les compilateurs.
Cole Johnson
3
J'ai lu le document avec la règle 17.5 sur plus de 2 niveaux d'indirection de pointeur. Et cela n'interdit pas forcément plus de 2 niveaux. Il indique que la décision doit être suivie, car plus de 2 niveaux sont "non-compliant"conformes à leurs normes. Le mot ou l'expression important dans leur décision est l'utilisation du mot "should"de cette déclaration: 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.Ce sont des lignes directrices établies par cette organisation par opposition aux règles établies par la norme linguistique.
Francis Cugler
1

Il n'y a pas de véritable limite, mais la limite existe. Tous les pointeurs sont des variables qui sont généralement stockées dans la pile et non dans le tas . La pile est généralement petite (il est possible de changer sa taille lors de certaines liaisons). Disons donc que vous avez une pile de 4 Mo, ce qui est une taille tout à fait normale. Et disons que nous avons un pointeur qui a une taille de 4 octets (les tailles de pointeur ne sont pas les mêmes selon l'architecture, les paramètres de cible et de compilateur).

Dans ce cas 4 MB / 4 b = 1024, le nombre maximum possible serait 1048576, mais nous ne devons pas ignorer le fait que d'autres éléments sont en pile.

Cependant, certains compilateurs peuvent avoir un nombre maximal de chaînes de pointeurs, mais la limite est la taille de la pile. Donc, si vous augmentez la taille de la pile pendant la liaison avec l'infini et que vous avez une machine avec une mémoire à l'infini qui exécute un système d'exploitation qui gère cette mémoire, vous aurez une chaîne de pointeurs illimitée.

Si vous utilisez int *ptr = new int;et placez votre pointeur dans un tas, ce n'est pas la façon habituelle de limiter la taille du tas, pas la pile.

EDIT Réalisez juste cela infinity / 2 = infinity. Si la machine a plus de mémoire, la taille du pointeur augmente. Donc, si la mémoire est infinie et que la taille du pointeur est infinie, c'est donc une mauvaise nouvelle ... :)

ST3
la source
4
A) Les pointeurs peuvent être stockés sur le tas ( new int*). B) An int*et an int**********ont la même taille, au moins sur des architectures raisonnables.
@rightfold A) Oui, les pointeurs peuvent être stockés en tas. Mais ce serait quelque chose de très différent, comme la création d'un conteneur contenant des pointeurs vers le pointeur précédent suivant. B) Bien sûr, int*et ils int**********ont la même taille, je n'ai pas dit qu'ils étaient différents.
ST3
2
Ensuite, je ne vois pas comment la taille de la pile est pertinente à distance.
@rightfold J'ai pensé à la façon habituelle de distribuer les données lorsque toutes les données sont en tas et sur la pile, ce ne sont que des pointeurs vers ces données. Ce serait la manière habituelle , mais je conviens qu'il est possible de mettre des pointeurs en pile.
ST3
"Bien sûr, int * et un int ********** ont la même taille" - la norme ne garantit pas cela (même si je ne connais aucune plate-forme où ce n'est pas vrai).
Martin Bonner soutient Monica
0

Cela dépend de l'endroit où vous stockez les pointeurs. S'ils sont en pile, vous avez une limite assez basse . Si vous le stockez en tas, votre limite est beaucoup plus élevée.

Regardez ce programme:

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

Il crée des pointeurs 1M et lors des spectacles, quel est le point vers ce qu'il est facile de remarquer ce que la chaîne va à la première variable number .

BTW. Il utilise 92Kde la RAM, alors imaginez à quelle profondeur vous pouvez aller.

ST3
la source