¿Cuántos niveles de punteros podemos tener?


443

¿Cuántos punteros ( *) están permitidos en una sola variable?

Consideremos el siguiente ejemplo.

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

Del mismo modo podemos tener

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

y así.

Por ejemplo,

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

582
Si eso se convierte en un problema real para usted, está haciendo algo muy malo.
ThiefMaster

279
Puede seguir agregando niveles de punteros hasta que su cerebro explote o el compilador se derrita, lo que ocurra más pronto.
JeremyP

47
Dado que un puntero a un puntero es nuevamente, bueno, solo un puntero, no debería haber ningún límite teórico. Quizás el compilador no podrá manejarlo más allá de un límite ridículamente alto, pero bueno ...
Christian Rau

73
con el nuevo c ++ deberías usar algo comostd::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx

44
@josefx: esto muestra un problema en el estándar C ++: no hay forma de elevar los punteros inteligentes a los poderes. Debemos exigir inmediatamente una extensión para admitir, por ejemplo, (pow (std::shared_ptr, -0.3))<T> x;niveles de indirecta de -0.3
Steve314

Respuestas:


400

El Cestándar especifica el límite inferior:

5.2.4.1 Límites de traducción

276 La implementación podrá traducir y ejecutar al menos un programa que contenga al menos una instancia de cada uno de los siguientes límites: [...]

279 - 12 indicadores de puntero, matriz y función (en cualquier combinación) que modifiquen un tipo aritmético, estructura, unión o vacío en una declaración

El límite superior es específico de la implementación.


121
El estándar C ++ "recomienda" que una implementación admita al menos 256. (La legibilidad recomienda que no exceda 2 o 3, e incluso entonces: más de uno debería ser excepcional).
James Kanze

22
Este límite es sobre cuántos en una sola declaración; no impone un límite superior sobre la cantidad de indirección que puede lograr a través de múltiples typedefs.
Kaz

11
@Kaz: sí, eso es cierto. Pero dado que la especificación es (sin juego de palabras) que especifica un límite inferior obligatorio, es el límite superior efectivo que todos los compiladores que cumplen con las especificaciones deben admitir. Podría ser más bajo que el límite superior específico del proveedor, por supuesto. Reformulado de manera diferente (para alinearlo con la pregunta del OP), es el máximo permitido por la especificación (cualquier otra cosa sería específica del proveedor). Un poco fuera de la tangente, los programadores deberían (al menos en el caso general) tratar eso como su límite superior (a menos que tengan una razón válida para confiar en un límite superior específico del proveedor) ... piensa.
luis.espinal

55
En otra nota, comenzaría a cortarme si tuviera que trabajar con el código que tenía cadenas de referencia largas (especialmente cuando abundantemente salpicado por todo el lugar .)
luis.espinal

11
@berilio: por lo general, estos números provienen de una encuesta de software de preestandarización. En este caso, presumiblemente, observaron programas comunes de C y compiladores de C existentes, y encontraron al menos un compilador que tendría problemas con más de 12 y / o ningún programa que no funcionara si lo restringía a 12.

155

En realidad, los programas en C suelen utilizar la indirecta de puntero infinito. Uno o dos niveles estáticos son comunes. La triple indirección es rara. Pero infinito es muy común.

La indirección de puntero infinito se logra con la ayuda de una estructura, por supuesto, no con un declarador directo, lo que sería imposible. Y se necesita una estructura para que pueda incluir otros datos en esta estructura en los diferentes niveles donde esto puede terminar.

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

ahora puedes tener list->next->next->next->...->next. Esto es realmente sólo múltiples indirecciones puntero: *(*(..(*(*(*list).next).next).next...).next).next. Y .nextes básicamente un noop cuando es el primer miembro de la estructura, por lo que podemos imaginar esto como ***..***ptr.

Realmente no hay límite para esto porque los enlaces se pueden atravesar con un bucle en lugar de una expresión gigante como esta, y además, la estructura se puede hacer fácilmente circular.

Por lo tanto, en otras palabras, las listas vinculadas pueden ser el mejor ejemplo de agregar otro nivel de indirección para resolver un problema, ya que lo está haciendo dinámicamente con cada operación de inserción. :)


48
Sin embargo, ese es un problema completamente diferente: una estructura que contiene un puntero a otra estructura es muy diferente a un puntero-puntero. Un int ***** es un tipo distinto de un int ****.
esponjoso

12
No es "muy" diferente. La diferencia es esponjosa. Está más cerca de la sintaxis que de la semántica. ¿Un puntero a un objeto puntero o un puntero a un objeto de estructura que contiene un puntero? Es el mismo tipo de cosas. Llegar al décimo elemento de una lista son diez niveles de direccionamiento indirecto. (Por supuesto, la capacidad de expresar una estructura infinita depende del tipo struct ser capaz de apuntar a sí mismo a través del tipo struct incompleta de manera que list->nexty list->next->nextson del mismo tipo, de lo contrario habría que construir un tipo de infinito.)
Kaz

34
No noté conscientemente que tu nombre es esponjoso cuando usé la palabra "esponjoso". Influencia subconsciente? Pero estoy seguro de que he usado la palabra de esta manera antes.
Kaz

3
También recuerde que en lenguaje de máquina, simplemente podría iterar en algo así LOAD R1, [R1], siempre que R1 sea un puntero válido en cada paso. No hay tipos involucrados que no sean "palabra que contiene una dirección". Si hay tipos declarados o no, no determina la indirección y cuántos niveles tiene.
Kaz

44
No si la estructura es circular. Si R1contiene la dirección de una ubicación que apunta a sí misma, LOAD R1, [R1]puede ejecutarse en un bucle infinito.
Kaz

83

Teóricamente:

Puede tener tantos niveles de indirecciones como desee.

Prácticamente:

Por supuesto, nada que consuma memoria puede ser indefinido, habrá limitaciones debido a los recursos disponibles en el entorno del host. Por lo tanto, prácticamente existe un límite máximo para lo que una implementación puede soportar y la implementación debe documentarlo adecuadamente. Entonces, en todos estos artefactos, el estándar no especifica el límite máximo, pero sí especifica los límites inferiores.

Aquí está la referencia:

Estándar C99 5.2.4.1 Límites de traducción:

- 12 indicadores de puntero, matriz y función (en cualquier combinación) que modifiquen un tipo aritmético, estructura, unión o vacío en una declaración.

Esto especifica el límite inferior que toda implementación debe soportar. Tenga en cuenta que en una nota a pie de página el estándar dice además:

18) Las implementaciones deben evitar imponer límites de traducción fijos siempre que sea posible.


16
¡las indirecciones no desbordan ninguna pila!
Basile Starynkevitch

1
Corregido, tuve este sentimiento errado de leer y responder la q como límite de parámetros que se pasan a la función. ¡No sé por qué!
Alok Save

2
@basile: esperaría que la profundidad de la pila sea un problema en el analizador. Muchos algoritmos de análisis formales tienen una pila como componente clave. La mayoría de los compiladores de C ++ probablemente usan una variante de descenso recursivo, pero incluso eso se basa en la pila del procesador (o, pedantemente, en el lenguaje que actúa como si hubiera una pila de procesador). Más anidamiento de las reglas gramaticales significa una pila más profunda.
Steve314

8
¡las indirecciones no desbordan ninguna pila! -> ¡No! la pila del analizador puede desbordarse. ¿Cómo se relaciona la pila con la indirección del puntero? Pila de analizador!
Pavan Manjunath

Si *se sobrecarga para varias clases en una fila, y cada sobrecarga devuelve un objeto de otro tipo en la fila, entonces puede haber un desbordamiento de pila para tales llamadas de función encadenadas.
Nawaz

76

Como la gente ha dicho, no hay límite "en teoría". Sin embargo, por interés, ejecuté esto con g ++ 4.1.2, y funcionó con un tamaño de hasta 20,000. Sin embargo, la compilación fue bastante lenta, así que no intenté más alto. Así que supongo que g ++ tampoco impone ningún límite. (Intente configurar size = 10y buscar en ptr.cpp si no es inmediatamente obvio).

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
No pude obtener más de 98242 cuando lo probé. (Hice el script en Python, duplicando el número de *hasta que obtuve uno que falló y el anterior que pasó; luego hice una búsqueda binaria en ese intervalo para el primero que falló. Toda la prueba tomó menos de un segundo para correr.)
James Kanze

63

Suena divertido comprobarlo.

  • Visual Studio 2010 (en Windows 7), puede tener niveles de 1011 antes de recibir este error:

    error fatal C1026: desbordamiento de la pila del analizador, programa demasiado complejo

  • gcc (Ubuntu), 100k + *sin un bloqueo! Supongo que el hardware es el límite aquí.

(probado con solo una declaración variable)


55
De hecho, las producciones para operadores unarios son recursivas a la derecha, lo que significa que un analizador de reducción de desplazamiento desplazará todos los *nodos a la pila antes de poder hacer una reducción.
Kaz

28

No hay límite, consulte el ejemplo aquí .

La respuesta depende de lo que quiere decir con "niveles de punteros". Si quiere decir "¿Cuántos niveles de indirección puede tener en una sola declaración?" la respuesta es "Al menos 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 quiere decir "cuántos niveles de puntero puede usar antes de que el programa se vuelva difícil de leer", eso es cuestión de gustos, pero hay un límite. Tener dos niveles de indirección (un puntero a un puntero a algo) es común. Más de eso se vuelve un poco más difícil de pensar fácilmente; no lo hagas a menos que la alternativa sea peor.

Si quiere decir "Cuántos niveles de indirección de puntero puede tener en tiempo de ejecución", no hay límite. Este punto es particularmente importante para las listas circulares, en las que cada nodo apunta al siguiente. Su programa puede seguir los punteros para siempre.


77
Es casi seguro que hay un límite, ya que el compilador debe realizar un seguimiento de la información en una cantidad finita de memoria. ( g++aborta con un error interno en 98242 en mi máquina. Espero que el límite real dependa de la máquina y la carga. Tampoco espero que esto sea un problema en el código real).
James Kanze

2
Sí @MatthieuM. : Acabo de considerar teóricamente :) Gracias James por completar la respuesta
Nandkumar Tekale

3
Bueno, las listas vinculadas no son realmente un puntero a un puntero, son un puntero a una estructura que contiene un puntero (o eso o terminas haciendo un montón de conversión innecesaria)
Random832

1
@ Random832: Nand dijo 'Si te refieres a "Cuántos niveles de indirección de puntero puedes tener en tiempo de ejecución", entonces él eliminó explícitamente la restricción de solo hablar de punteros a punteros (* n).
LarsH

1
No entiendo su punto: ' No hay límite, consulte el ejemplo aquí. 'El ejemplo no es prueba de que no haya límite. Solo prueba que es posible una indirección de 12 estrellas. Tampoco prueba nada el circ_listejemplo con respecto a la pregunta del OP: El hecho de que pueda atravesar una lista de punteros no implica que el compilador pueda compilar una dirección indirecta n-estrellas.
Alberto

24

En realidad, es aún más divertido con el puntero a las funciones.

#include <cstdio>

typedef void (*FuncType)();

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

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

Como se ilustra aquí, esto da:

¡Hola Mundo!
¡Hola Mundo!
¡Hola Mundo!

Y no implica ninguna sobrecarga de tiempo de ejecución, por lo que probablemente pueda apilarlos tanto como desee ... hasta que su compilador se ahoga en el archivo.


20

No hay limite . Un puntero es una porción de memoria cuyo contenido es una dirección.
Como dijiste

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

Un puntero a un puntero también es una variable que contiene una dirección de otro puntero.

int **q = &p;

Aquí qhay un puntero a puntero que contiene la dirección de la pcual ya contiene la dirección de a.

No hay nada particularmente especial sobre un puntero a un puntero.
Por lo tanto, no hay límite en la cadena de poniters que contienen la dirección de otro puntero.
es decir.

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

esta permitido.


17

Todos los desarrolladores de C ++ deberían haber oído hablar del (in) famoso programador de tres estrellas

Y realmente parece haber alguna "barrera de puntero" mágica que debe camuflarse

Cita de C2:

Programador de tres estrellas

Un sistema de calificación para programadores en C. Cuanto más indirectos sean sus punteros (es decir, más "*" antes de sus variables), mayor será su reputación. Los programadores C sin estrellas son prácticamente inexistentes, ya que prácticamente todos los programas no triviales requieren el uso de punteros. La mayoría son programadores de una estrella. En los viejos tiempos (bueno, soy joven, así que al menos me parecen viejos tiempos), de vez en cuando uno encontraba un código hecho por un programador de tres estrellas y temblaba de asombro. Algunas personas incluso afirmaron haber visto un código de tres estrellas con punteros de función involucrados, en más de un nivel de indirección. Me pareció tan real como los ovnis.


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… y similares fueron escritos por un programador de 4 estrellas. También es un amigo mío y, si lees el código lo suficiente, entenderás la razón por la que merece 4 estrellas.
Jeff

13

Tenga en cuenta que aquí hay dos posibles preguntas: cuántos niveles de indirección de puntero podemos lograr en un tipo C y cuántos niveles de indirección de puntero podemos meter en un solo declarador.

El estándar C permite imponer un máximo al primero (y le da un valor mínimo para eso). Pero eso se puede evitar a través de múltiples declaraciones typedef:

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

Así que, en última instancia, este es un problema de implementación relacionado con la idea de cuán grande / complejo puede hacerse un programa C antes de ser rechazado, lo cual es muy específico del compilador.


4

Me gustaría señalar que producir un tipo con un número arbitrario de * es algo que puede suceder con la metaprogramación de plantillas. Olvidé lo que estaba haciendo exactamente, pero se sugirió que podría producir nuevos tipos distintos que tengan algún tipo de meta maniobra entre ellos mediante el uso de tipos T * recursivos .

La metaprogramación de plantillas es un lento descenso a la locura, por lo que no es necesario poner excusas al generar un tipo con varios miles de niveles de indirección. Es solo una forma práctica de mapear enteros peano, por ejemplo, en la expansión de plantillas como lenguaje funcional.


Admito que no entiendo tu respuesta completamente, pero me ha dado una nueva área para explorar. :)
ankush981

3

La regla 17.5 de la norma MISRA C 2004 prohíbe más de 2 niveles de puntero indirecto.


15
Estoy bastante seguro de que es una recomendación para programadores, no para compiladores.
Cole Johnson

3
Leí el documento con la regla 17.5 sobre más de 2 niveles de indirección de puntero. Y no necesariamente prohíbe más de 2 niveles. Establece que la decisión debe seguirse ya que más de 2 niveles cumplen "non-compliant"con sus estándares. La palabra o frase importante en su decisión es el uso de la palabra "should"de esta declaración: 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.Estas son pautas establecidas por esta organización en oposición a las reglas establecidas por el estándar del idioma.
Francis Cugler

1

No existe el límite real, pero el límite existe. Todos los punteros son variables que generalmente se almacenan en la pila, no en el montón . La pila suele ser pequeña (es posible cambiar su tamaño durante algunos enlaces). Entonces, digamos que tiene una pila de 4 MB, lo que es un tamaño bastante normal. Y digamos que tenemos un puntero que tiene un tamaño de 4 bytes (los tamaños del puntero no son los mismos dependiendo de la arquitectura, el destino y la configuración del compilador).

En este caso, el 4 MB / 4 b = 1024número máximo posible sería 1048576, pero no debemos ignorar el hecho de que hay otras cosas en la pila.

Sin embargo, algunos compiladores pueden tener un número máximo de cadena de puntero, pero el límite es el tamaño de la pila. Entonces, si aumenta el tamaño de la pila durante el enlace con infinito y tiene una máquina con memoria infinita que ejecuta el SO que maneja esa memoria, tendrá una cadena de puntero ilimitada.

Si usa int *ptr = new int;y coloca el puntero en el montón, esa no es la forma habitual de limitar el tamaño del montón, no la pila.

EDITAR Solo date cuenta de eso infinity / 2 = infinity. Si la máquina tiene más memoria, entonces el tamaño del puntero aumenta. Entonces, si la memoria es infinita y el tamaño del puntero es infinito, entonces son malas noticias ... :)


44
A) Los punteros se pueden almacenar en el montón ( new int*). B) An int*y an int**********tienen el mismo tamaño, al menos en arquitecturas razonables.

@rightfold A) Sí, los punteros se pueden almacenar en el montón. Pero sería algo muy diferente como crear un contenedor que contenga punteros que apuntan al siguiente puntero anterior. B) Por supuesto, int*y int**********tienen el mismo tamaño, no dije que tenían diferentes.
ST3

2
Entonces no veo cómo el tamaño de la pila es remotamente relevante.

@rightfold He estado pensando en la forma habitual de distribución de datos cuando todos los datos están en el montón y en la pila, son solo punteros a esos datos. Sería la forma habitual , pero estoy de acuerdo en que es posible poner punteros en la pila.
ST3

"Por supuesto, int * y int ********** tienen el mismo tamaño", el estándar no garantiza eso (aunque no conozco ninguna plataforma donde no sea cierto).
Martin Bonner apoya a Monica el

0

Depende del lugar donde guarde los punteros. Si están en la pila, tienes un límite bastante bajo . Si lo almacena en el montón, el límite es mucho más alto.

Mira este programa:

#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 punteros de 1M y en los espectáculos qué punto a qué es fácil notar qué va la cadena a la primera variable number.

Por cierto. Utiliza 92KRAM, así que imagina cuán profundo puedes llegar.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.