¿Cómo funcionan los punteros a punteros en C? (¿y cuándo podría usarlos?)

12 minutos de lectura

¿Cómo funcionan los punteros a punteros en C?
¿Cuándo podrías usarlos?

  • No, no es tarea… solo quería saber… porque lo veo mucho cuando leo el código C.

    depurador

    22 de mayo de 2009 a las 11:18

  • Un puntero a puntero no es un caso especial de algo, por lo que no entiendo lo que no entiendes sobre el vacío **.

    – Akappa

    22 de mayo de 2009 a las 11:19

  • para matrices 2D, el mejor ejemplo es la línea de comando args “prog arg1 arg2” se almacena char ** argv. Y si la persona que llama no quiere asignar la memoria (la función llamada asignará la memoria)

    – camino de resultados

    21 de marzo de 2013 a las 22:29


  • Tiene un buen ejemplo del uso de “puntero a puntero” en Git 2.0: vea mi respuesta a continuación

    – VoC

    12/03/2014 a las 14:40

avatar de usuario
Stephan202

Supongamos una computadora de 8 bits con direcciones de 8 bits (y por lo tanto solo 256 bytes de memoria). Esto es parte de esa memoria (los números en la parte superior son las direcciones):

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

Lo que puede ver aquí es que en la dirección 63 comienza la cadena “hola”. Entonces, en este caso, si esta es la única aparición de “hola” en la memoria, entonces,

const char *c = "hello";

… define c ser un puntero a la cadena (de solo lectura) “hola” y, por lo tanto, contiene el valor 63. c debe almacenarse en algún lugar: en el ejemplo anterior, en la ubicación 58. Por supuesto, no solo podemos señalar caracteres, sino también otros punteros. P.ej:

const char **cp = &c;

Ahora cp puntos a ces decir, contiene la dirección de c (que es 58). Podemos ir aún más lejos. Considerar:

const char ***cpp = &cp;

Ahora cpp almacena la dirección de cp. Por lo tanto, tiene el valor 55 (según el ejemplo anterior), y lo adivinó: se almacena en la dirección 60.


En cuanto a por qué uno usa punteros a punteros:

  • El nombre de una matriz generalmente produce la dirección de su primer elemento. Entonces, si la matriz contiene elementos de tipo tuna referencia a la matriz tiene tipo t *. Ahora considere una matriz de matrices de tipo t: naturalmente, una referencia a esta matriz 2D tendrá tipo (t *)* = t **y por lo tanto es un puntero a un puntero.
  • Aunque una matriz de cadenas suena unidimensional, en realidad es bidimensional, ya que las cadenas son matrices de caracteres. Por lo tanto: char **.
  • Una función f tendrá que aceptar un argumento de tipo t ** si es para alterar una variable de tipo t *.
  • Muchas otras razones que son demasiado numerosas para enumerarlas aquí.

  • sí, buen ejemplo… entiendo lo que son… pero cómo y cuándo usarlos es más importante… ahora…

    depurador

    22 de mayo de 2009 a las 11:33

  • Stephan hizo un buen trabajo al reproducir, básicamente, el diagrama en The C Programming Language de Kernighan & Richie. Si está programando en C, y no tiene este libro y no le molesta la documentación en papel, le sugiero que lo obtenga, el gasto (bastante) modesto se amortizará muy rápidamente en productividad. Tiende a ser muy claro en sus ejemplos.

    – J. Polfer

    22 de mayo de 2009 a las 13:33

  • char* c = “hola” debe ser const char* c = “hola”. También es engañoso decir que “una matriz se almacena como la dirección del primer elemento”. Una matriz se almacena como… una matriz. A menudo, su nombre apunta a su primer elemento, pero no siempre. Acerca de los punteros a punteros, simplemente diría que son útiles cuando una función tiene que modificar un puntero pasado como parámetro (entonces pasas un puntero al puntero en su lugar).

    – Bastien Leonard

    22 de mayo de 2009 a las 20:12

  • A menos que esté malinterpretando esta respuesta, parece incorrecta. c se almacena en 58 y apunta a 63, cp se almacena en 55 y apunta a 58 y cpp no ​​se representa en el diagrama.

    – Tánatos

    23 de mayo de 2009 a las 16:16

  • Se ve bien. Ese problema menor fue todo lo que me impidió decir: Gran publicación. La explicación en sí fue excelente. Cambiando a un voto a favor. (¿Quizás stackoverflow necesita revisar los punteros?)

    – Tánatos

    23 de mayo de 2009 a las 16:54

avatar de usuario
Brian R. Bondy

¿Cómo funcionan los punteros a punteros en C?

Primero, un puntero es una variable, como cualquier otra variable, pero que contiene la dirección de una variable.

Un puntero a un puntero es una variable, como cualquier otra variable, pero que contiene la dirección de una variable. Esa variable resulta ser un puntero.

¿Cuándo los usarías?

Puede usarlos cuando necesite devolver un puntero a alguna memoria en el montón, pero sin usar el valor de retorno.

Ejemplo:

int getValueOf5(int *p)
{
  *p = 5;
  return 1;//success
}

int get1024HeapMemory(int **p)
{
  *p = malloc(1024);
  if(*p == 0)
    return -1;//error
  else 
    return 0;//success
}

Y lo llamas así:

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5

int *p;    
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

También hay otros usos, como que el argumento main() de cada programa C tiene un puntero a un puntero para argv, donde cada elemento contiene una matriz de caracteres que son las opciones de la línea de comandos. Sin embargo, debe tener cuidado cuando usa punteros de punteros para apuntar a matrices bidimensionales, es mejor usar un puntero a una matriz bidimensional en su lugar.

¿Por qué es peligroso?

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

Aquí hay un ejemplo de un puntero a una matriz bidimensional hecho correctamente:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

Sin embargo, no puede usar un puntero a una matriz bidimensional si desea admitir una cantidad variable de elementos para FILAS y COLUMNAS. Pero cuando sabe de antemano, usaría una matriz bidimensional.

Considere la siguiente figura y programa para comprender mejor este concepto.

Diagrama de doble puntero

Según la figura, ptr1 es un puntero único que tiene dirección de variable número.

ptr1 = #

Similarmente ptr2 es un puntero a puntero (puntero doble) que tiene la dirección del puntero ptr1.

ptr2 = &ptr1;

Un puntero que apunta a otro puntero se conoce como puntero doble. En este ejemplo ptr2 es un puntero doble.

Valores del diagrama anterior:

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

Ejemplo:

#include <stdio.h>

int main ()
{
   int  num = 10;
   int  *ptr1;
   int  **ptr2;

   // Take the address of var 
   ptr1 = &num;

   // Take the address of ptr1 using address of operator &
   ptr2 = &ptr1;

   // Print the value
   printf("Value of num = %d\n", num );
   printf("Value available at *ptr1 = %d\n", *ptr1 );
   printf("Value available at **ptr2 = %d\n", **ptr2);
}

Producción:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10

avatar de usuario
VonC

Me gusta este ejemplo de código del “mundo real” del uso de puntero a puntero, en Git 2.0, cometer 7b1004b:

Linus dijo una vez:

De hecho, desearía que más personas entendieran el tipo de codificación realmente central de bajo nivel. No cosas grandes y complejas como la búsqueda de nombres sin bloqueo, sino simplemente un buen uso de punteros a punteros, etc.
Por ejemplo, he visto a demasiadas personas que eliminan una entrada de lista con un solo enlace haciendo un seguimiento de la entrada “anterior” y luego eliminan la entrada, haciendo algo como:


   if (prev)
     prev->next = entry->next;
   else
     list_head = entry->next;

y cada vez que veo un código como ese, simplemente digo “Esta persona no entiende los punteros”. Y lamentablemente es bastante común.

Las personas que entienden los punteros solo usan un “puntero al puntero de entrada“, e inicialice eso con la dirección de list_head. Y luego, a medida que atraviesan la lista, pueden eliminar la entrada sin usar ningún condicional, simplemente haciendo un

*pp =  entry->next

punteros

Aplicar esa simplificación nos permite perder 7 líneas de esta función incluso al agregar 2 líneas de comentario.

- struct combine_diff_path *p, *pprev, *ptmp;
+ struct combine_diff_path *p, **tail = &curr;

Chris señala en los comentarios al video de 2016 “El problema del doble puntero de Linus Torvalds“.


kumar señala en los comentarios la publicación del blog “Linus sobre la comprensión de los punteros“, donde Grisha Trubetskoy explica:

Imagina que tienes una lista enlazada definida como:

   typedef struct list_entry {
       int val;
       struct list_entry *next;
   } list_entry;

Debe iterarlo de principio a fin y eliminar un elemento específico cuyo valor sea igual al valor de to_remove.
La forma más obvia de hacer esto sería:

   list_entry *entry = head; /* assuming head exists and is the first entry of the list */
   list_entry *prev = NULL;
   
   while (entry) { /* line 4 */
       if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
           if (prev)
              prev->next = entry->next; /* remove the entry ; line 7 */
           else
               head = entry->next;      /* special case - first entry ; line 9 */
   
       /* move on to the next entry */
       prev = entry;
       entry = entry->next;
   }

Lo que estamos haciendo arriba es:

  • iterando sobre la lista hasta que la entrada sea NULLlo que significa que hemos llegado al final de la lista (línea 4).
  • Cuando nos encontramos con una entrada que queremos eliminar (línea 5),
  • asignamos el valor del siguiente puntero actual al anterior,
  • eliminando así el elemento actual (línea 7).

Hay un caso especial arriba: al comienzo de la iteración no hay una entrada anterior (prev es NULL), por lo que para eliminar la primera entrada de la lista debe modificar head (línea 9).

Lo que Linus estaba diciendo es que el código anterior podría simplificarse haciendo que el elemento anterior sea un puntero a un puntero en lugar de solo un puntero.
El código entonces se ve así:

   list_entry **pp = &head; /* pointer to a pointer */
   list_entry *entry = head;

   while (entry) {
       if (entry->val == to_remove)
           *pp = entry->next;
       else
            pp = &entry->next;
       entry = entry->next;
   }

El código anterior es muy similar a la variante anterior, pero observe cómo ya no necesitamos estar atentos al caso especial del primer elemento de la lista, ya que pp no es NULL al principio. Simple e inteligente.

Además, alguien en ese hilo comentó que la razón por la que esto es mejor es porque *pp = entry->next es atómico. Ciertamente NO es atómico.
La expresión anterior contiene dos operadores de desreferencia (* y ->) y una asignación, y ninguna de esas tres cosas es atómica.
Este es un concepto erróneo común, pero, por desgracia, casi nada en C debe asumirse como atómico. (incluyendo el ++ y -- operadores)!

avatar de usuario
alex balashov

Un puntero a un puntero se utiliza cuando se requiere una referencia a un puntero. Por ejemplo, cuando desea modificar el valor (dirección apuntada) de una variable de puntero declarada en el alcance de una función de llamada dentro de una función llamada.

Si pasa un solo puntero como argumento, estará modificando copias locales del puntero, no el puntero original en el ámbito de llamada. Con un puntero a un puntero, modificas este último.

  • Bien explicado para la parte ‘Por qué’

    – Rana profundo

    16 de febrero de 2014 a las 9:53

avatar de usuario
Comunidad

Cuando cubríamos los consejos de un curso de programación en la universidad, nos dieron dos pistas sobre cómo comenzar a aprender sobre ellos. La primera fue para ver puntero divertido con binky. La segunda era pensar en el ojos de eglefino pasaje de Lewis Carroll Através del espejo

“Estás triste”, dijo el Caballero con tono ansioso: “Déjame cantarte una canción para consolarte”.

“¿Es muy largo?” preguntó Alice, porque había escuchado mucha poesía ese día.

“Es largo”, dijo el Caballero, “pero es muy, muy hermoso. Todos los que me escuchan cantarla, o les hace llorar, o si no…

“¿O si no qué?” dijo Alicia, porque el Caballero había hecho una pausa repentina.

“O de lo contrario no lo hace, ya sabes. El nombre de la canción se llama ‘Haddocks’ Eyes’”.

“Oh, ese es el nombre de la canción, ¿verdad?” dijo Alice, tratando de mostrarse interesada.

“No, no lo entiendes,” dijo el Caballero, luciendo un poco molesto. “Así es como se llama el nombre. El nombre realmente es ‘The Aged Aged Man’”.

“¿Entonces debí haber dicho ‘Así se llama la canción’?” Alicia se corrigió a sí misma.

“No, no deberías: ¡eso es otra cosa! La canción se llama Ways and Means: pero así es como se llama, ¿sabes?

“Bueno, ¿cuál es la canción, entonces?” dijo Alice, quien en ese momento estaba completamente desconcertada.

“Estaba llegando a eso”, dijo el Caballero. “La canción realmente es ‘A-sitting On A Gate’: y la melodía es mi propia invención”.

  • Bien explicado para la parte ‘Por qué’

    – Rana profundo

    16 de febrero de 2014 a las 9:53

avatar de usuario
lucas schafer

es un puntero al valor de la dirección del puntero. (Eso es terrible, lo sé)

básicamente, le permite pasar un puntero al valor de la dirección de otro puntero, para que pueda modificar hacia dónde apunta otro puntero desde una subfunción, como:

void changeptr(int** pp)
{
  *pp=&someval;
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad