Punteros dobles / Punteros a puntero.

Iniciado por NOB2014, 13 Abril 2016, 02:58 AM

0 Miembros y 1 Visitante están viendo este tema.

NOB2014

Hola, a todos.
Desearía  que alguien me ayude a entender la utilidad práctica de los punteros dobles, entiendo que una variable puntero apunta a la dirección de memoria donde está alojada una variable del mismo tipo, o sea su valor es la dirección de memoria de una variable común.-
Un puntero doble apunta a otro puntero, si bien mi descripción no es para escribir un libro pero es lo que hay.-
Por ahí leí que los punteros dobles se asemejan a los array de 2 dimensiones, es esto cierto y porque?.-
El manual con el que estoy estudiando punteros pone este ejemplo pero no tengo claro el beneficio en utilizar punteros dobles, el autor en este caso no da demasiadas explicaciones sobre el tema, no me cabe dudas que más adelante profundizara  sobre el particular pero me gustaría aprenderlo de Uds. El libro tiene más de 200 páginas dedicadas exclusivamente a punteros y de paso está en inglés.-

#include <stdio.h>

int main( void ){
char *titles[] = {"A Tale of Two Cities",
"Wuthering Heights","Don Quixote",
"Odyssey","Moby-Dick","Hamlet",
"Gulliver's Travels"};

char **bestBooks[3];
char **englishBooks[4];

bestBooks[0] = &titles[0];
bestBooks[1] = &titles[3];
bestBooks[2] = &titles[5];
englishBooks[0] = &titles[0];
englishBooks[1] = &titles[1];
englishBooks[2] = &titles[5];
englishBooks[3] = &titles[6];

printf("%s\n\n",*englishBooks[1]); /*Wuthering Heights*/

return 0;
}
   

Bueno es todo por el momento y desde ya muchas gracias.-
Daniel
abraza las cosas y personas malas como si fueran tu mas preciada joya,Son tus mas grandes maestros de paciencia sabiduría y amor y cuando lo abrazas dejan de causar dolor.-

AlbertoBSD

#1
Un beneficio de utilizar dobles puntero dependiendo de lo que vallas a hacer con tu programa es hacer mejor uso de memoria dinamica.

Hace unos dias participe en el siguiente tema    
Re: Sistema de cifrado por lote de texto plano


Deje el codigo y utliza dobles punteros para almacenar unas listas de longitud dinámica cada lista.

El detalle que si los usas mal y/o no asginas y/o liberas memoria incorrectamente terminaras haciendo un completo desmadre y posiblemente el programa termine colgándose.

Imaginate esto, quieres hacer un programa que almacene las calificaciones de todos los alumnos de una escuela para despues hacer cualquier otro proceso con ellos.


int **calificaciones;


Estamos de acuerdo que la instrucción anterior solo "reserva" en la pila un espacio para guardar un apuntador.


int i,j;
int *alumnosXsalon;
int **calificaciones;


Mientras tanto en la pila se muestra asi (Uso 4 bytes para los int, y para los apuntadores asumo que estamos en un sistema x86):

Pila:

[4 bytes variable calificaciones] << === Un apuntador doble
[4 bytes variable alumnosXsalon] <<=== Un apuntador
[4 bytes variable j]
[4 bytes variable i]



Asignamos memoria para nuestra variable calificaciones Asumimos que son 30 salones, incluso se podria ser variable menos salones o mas salones incluso mil o un millon suponiendo que hay memoria suficiente no sera problema reservarla.


calificaciones = calloc(30,sizeof(int*));


Como mencione si el apuntador es de 4 bytes, se nos devolvera una direccion de memoria de longitud 30x4 bytes y como es la funcion calloc la memoria esta limpia esto es los 120 bytes estaran en 0 cada uno.

calificaciones => [Bloque de 120 continuos]

ahora bien la variable calificaciones apunta a un espacio en memoria de 120 bytes donde podemos guardar lo que queramos, pero siguiendo con el ejemplo de apuntadores damos por hecho que cada 4 bytes es un apuntador a otra direccion de memoria (Actualmente no apuntan a ningun lugar ya que no hemos inicializado esos 30 apuntadores).

Como es un ejemplo didactico supondre que alumnosXsalon ya esta inicializado y es un arreglo 30 de enteros y cada uno indica la cantidad de alumnos en cada salon por ejemplo

alumnosXsalon = {20, 30, 10 , 24, 12, 3, 8, 18 , .....}

Ahora vamos a inicializar esos 30 apuntadores:


i = 0
while(i < 30) {
calificaicones[i] = calloc(alumnosXsalon[i],sizeof(int));
i++;
}


Con esto logramos reservamos la memoria para todas las calificaciones.

calificaciones ==>> [120 bytes, cada 4 bytes es un apuntador a otra segmento de memoria]

o ma claro aun

calificaciones => [[1ros apuntador => 20 enteros],[2do apuntador => 30 enteros], .... ]

Y ahora solo queria escribir las cientos de calificaciones en sus respectivos espacios asignados

Para entenderlo mejor ha que aprender a depurar un poco el código y mostrar las direcciones de memoria sus valores antes y depues asi como a donde estan apuntando.

Si dominas esto entenderas muy bien los apuntadores lo cual es una de las mejores caracteristicas del lenguaje C

Ejemplo comentado:

#include<stdio.h>
#include<stdlib.h>

int main() {
int i,j;
int alumnosXsalon[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30};
int **calificacion;
printf("i esta en 0x%.8x: [%i]\n",&i,i);
printf("j esta en 0x%.8x: [%i]\n",&j,j);
printf("alumnosXsalon esta en 0x%.8x: => %X\n",&alumnosXsalon,alumnosXsalon);
printf("calificacion esta en 0x%.8x: => %X aun no se reserva memori\n",&calificacion,calificacion);
calificacion = calloc(30,sizeof(int*));
printf("calificacion esta en 0x%.8x: => %X ya se reservo la memoria\n",&calificacion,calificacion);
i=0;
while(i < 30) {
printf("Reservado %i bytes\n",(alumnosXsalon[i]*sizeof(int)));
calificacion[i] = calloc(alumnosXsalon[i],sizeof(int));
printf("calificacion[%i] esta en 0x%.8x: => %X un segmento de %i bytes \n",i,calificacion + i,calificacion[i],(alumnosXsalon[i]*sizeof(int)));
i++;
}
}


Salida


i esta en 0xbfbfe834: [0]
j esta en 0xbfbfe830: [0]
alumnosXsalon esta en 0xbfbfe7b8: => BFBFE7B8
calificacion esta en 0xbfbfe7b4: => 1 aun no se reserva memori
calificacion esta en 0xbfbfe7b4: => 2880B080 ya se reservo la memoria
Reservado 4 bytes
calificacion[0] esta en 0x2880b080: => 2880D050 un segmento de 4 bytes
Reservado 8 bytes
calificacion[1] esta en 0x2880b084: => 2880D058 un segmento de 8 bytes
Reservado 12 bytes
calificacion[2] esta en 0x2880b088: => 2880E030 un segmento de 12 bytes
Reservado 16 bytes
calificacion[3] esta en 0x2880b08c: => 2880E040 un segmento de 16 bytes
Reservado 20 bytes
calificacion[4] esta en 0x2880b090: => 2880F020 un segmento de 20 bytes
Reservado 24 bytes
calificacion[5] esta en 0x2880b094: => 2880F040 un segmento de 24 bytes
Reservado 28 bytes
calificacion[6] esta en 0x2880b098: => 2880F060 un segmento de 28 bytes
Reservado 32 bytes
calificacion[7] esta en 0x2880b09c: => 2880F080 un segmento de 32 bytes
Reservado 36 bytes
calificacion[8] esta en 0x2880b0a0: => 28810040 un segmento de 36 bytes
Reservado 40 bytes
calificacion[9] esta en 0x2880b0a4: => 28810070 un segmento de 40 bytes
Reservado 44 bytes
calificacion[10] esta en 0x2880b0a8: => 288100A0 un segmento de 44 bytes
Reservado 48 bytes
calificacion[11] esta en 0x2880b0ac: => 288100D0 un segmento de 48 bytes
Reservado 52 bytes
calificacion[12] esta en 0x2880b0b0: => 28811040 un segmento de 52 bytes
Reservado 56 bytes
calificacion[13] esta en 0x2880b0b4: => 28811080 un segmento de 56 bytes
Reservado 60 bytes
calificacion[14] esta en 0x2880b0b8: => 288110C0 un segmento de 60 bytes
Reservado 64 bytes
calificacion[15] esta en 0x2880b0bc: => 28811100 un segmento de 64 bytes
Reservado 68 bytes
calificacion[16] esta en 0x2880b0c0: => 28812060 un segmento de 68 bytes
Reservado 72 bytes
calificacion[17] esta en 0x2880b0c4: => 288120B0 un segmento de 72 bytes
Reservado 76 bytes
calificacion[18] esta en 0x2880b0c8: => 28812100 un segmento de 76 bytes
Reservado 80 bytes
calificacion[19] esta en 0x2880b0cc: => 28812150 un segmento de 80 bytes
Reservado 84 bytes
calificacion[20] esta en 0x2880b0d0: => 28813040 un segmento de 84 bytes
Reservado 88 bytes
calificacion[21] esta en 0x2880b0d4: => 288130A0 un segmento de 88 bytes
Reservado 92 bytes
calificacion[22] esta en 0x2880b0d8: => 28813100 un segmento de 92 bytes
Reservado 96 bytes
calificacion[23] esta en 0x2880b0dc: => 28813160 un segmento de 96 bytes
Reservado 100 bytes
calificacion[24] esta en 0x2880b0e0: => 28814080 un segmento de 100 bytes
Reservado 104 bytes
calificacion[25] esta en 0x2880b0e4: => 288140F0 un segmento de 104 bytes
Reservado 108 bytes
calificacion[26] esta en 0x2880b0e8: => 28814160 un segmento de 108 bytes
Reservado 112 bytes
calificacion[27] esta en 0x2880b0ec: => 288141D0 un segmento de 112 bytes
Reservado 116 bytes
calificacion[28] esta en 0x2880b0f0: => 2880B100 un segmento de 116 bytes
Reservado 120 bytes
calificacion[29] esta en 0x2880b0f4: => 2880B180 un segmento de 120 bytes


Respondiendo tus preguntas:

Por ahí leí que los punteros dobles se asemejan a los array de 2 dimensiones, es esto cierto y porque?.

No del todo, depende de como se usen.
Si declaras un array de 2 dimenciones por ejemplo 15x10 es y sera siempre de 15x10.
En cambio si apuntador lo declaras de 15 x 10 es en ese momento de 15x10 y se podria quedar asi o modificarse, liberar la memoria asginar algunos elementos (digamos los primeros 5) a una longitud de solo 7 por ejemplo. Si se asemejan paro para fines didacticos pero los apuntadores son mejores. Ve el ejemplo anterior tengo un apuntador dinamico de 30 apuntadores apuntado a 1, 2, 3, 4, .. respectivamente,
Donaciones
1Coffee1jV4gB5gaXfHgSHDz9xx9QSECVW

NOB2014

Mi nivel de aprendizaje no me da todavía para siquiera intentar hacerte preguntas por lo que no entiendo, igual muchas gracias por tu tiempo.-

Daniel
abraza las cosas y personas malas como si fueran tu mas preciada joya,Son tus mas grandes maestros de paciencia sabiduría y amor y cuando lo abrazas dejan de causar dolor.-

AlbertoBSD

Buen dia.

Entiendo lo que dices, en su momento yo tambien no entendia los apuntadores pero como lo mencione es una de las mejores caracteristicas del lenguaje.

Asumi erroneamente por tu pregunta (Dobles apuntadores) que ya sabias lo que es un apunador.

Un apuntador independientemente del tipo de variable que sea, es una variable que apunta a una direccion de memoria.
Donaciones
1Coffee1jV4gB5gaXfHgSHDz9xx9QSECVW

NOB2014

CitarAsumi erroneamente por tu pregunta (Dobles apuntadores) que ya sabias lo que es un apunador.

Citarentiendo que una variable puntero apunta a la dirección de memoria donde está alojada una variable del mismo tipo, o sea su valor es la dirección de memoria de una variable común.-

abraza las cosas y personas malas como si fueran tu mas preciada joya,Son tus mas grandes maestros de paciencia sabiduría y amor y cuando lo abrazas dejan de causar dolor.-

MAFUS

Apuntar a un puntero parece una redundancia pero tiene sus usos:

1. Construcción de una tabla de forma dinámica:
Para un compilador que no implemente el estándar C99 o superior la única forma de obtener una tabla de MxN de la que las dimensiones no se conocen en tiempo de programación (a partir de ahora en tiempo de compilación) es usando punteros e instrucciones alloc. Ejemplo:

int **tabla;
int m, n;
int i;

/* Se obtienen las dimensiones de la tabla m y n
* n para el número de filas
* m para el número de columnas
*/

/* Se genera el primer array dinámico que contendrá n filas */

tabla = malloc(sizeof(int*) * filas);

/* Las filas son punteros porqué contendrán la dirección de inicio
* del array columnas.
* Por tanto, para cada elemento de filas, reservamos una porción de
* memoria de m elementos.
*/

for(i = 0; i < filas; ++i)
    tabla[i] = malloc(sizeof(int) * columnas);  /* Se puede apreciar notación de array para un puntero */


2. Construcción de una tabla de diente de sierra.
Es parecido al anterior pero con la ventaja de que se gasta menos memoria.
Si, por ejemplo, se necesita una tabla de la siguiente forma:
123456
123
1
123456
123
Se puede generar de la siguiente manera:

int **tabla;
int m, n;
int i;

/* Obtenemos el número de filas: n */
tabla = malloc(sizeof(*int) * n);

for(i = 0; i < n; ++i) {

    /* Obtenemos el número de columnas m de la fila actual i */

    /* Creamos el array de m columnas */
    tabla[i] = malloc(sizeof(int) * m);
}


3. Cambiar la dirección a la que apunta un puntero e una función externa
Tenemos un dato definido por nosotros, por ejemplo una lista, y decidimos que tenga una funcinalidad, una función, que borre un nodo y deje ese puntero del nodo  NULL para no tener problemas extraños si más adelante se referenciara ese nodo borrado: al ser NULL se pararía el programa y se lanzaría un mensaje de error, con lo que nos ayudaría con la depuración.

La función sería así:

/* Supongamos lo siguiente */
typedef struct node_t {
    int i;
    node_t *next;
} node;

void free_node(node **n) {
    node *aux = *n;
    if(aux) {
        free(aux);
        *n = NULL;
}

/* === Código que llama a la función === */
/* Supongamos nodo como un elemento que ya tiene memoria asignada
* con malloc, es decir: *
* node *nodo = malloc(sizeof(node));
* o una función que genere e inicialice un dato node.
* Ya hemos trabajado con él y ahora debemos borrarlo. Además
* se debe llevar a NULL el puntero, por lo que haremos uso
* de la función free_node que forma parte de las funciones de éste tipo
* de datos.
*/

free_node(&nodo);


Hay más funcionalidades para punteros a punteros, pero estas son las más comunes. Ya las irás viendo a medida que avances con C.