2009-05-22 8 views
140

¿Cómo funcionan los punteros a los punteros en C? ¿Cuándo los usarías?¿Cómo funciona el puntero a punteros en C?

+1

es esta tarea? –

+34

No, no deberes .... solo quería saber ... porque lo veo mucho cuando leo el código C. –

+1

Un puntero al puntero no es un caso especial de algo, así que no entiendo lo que no entiendes sobre el vacío **. – akappa

Respuesta

3

Un puntero a puntero es, bueno, un puntero al puntero.

Un ejemplo de meaningfull SomeType ** es una matriz bidimensional: usted tiene una matriz, lleno de referencias a otras matrices, por lo que cuando se escribe

dpointer [5] [6]

el acceso al la matriz que contiene punteros a otras matrices en su quinta posición, obtenga el puntero (deje que su nombre sea fpo) y luego acceda al sexto elemento de la matriz referenciada a esa matriz (por lo tanto, fpointer [6]).

+2

Los punteros a los punteros no deben confundirse con las matrices de rank2, por ej. int x [10] [10] donde escribe x [5] [6] accede al valor en la matriz. –

+0

Este es solo un ejemplo donde un vacío ** es apropiado. Un puntero al puntero es solo un puntero que apunta, bueno, a un puntero. – akappa

4

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

básicamente, permite que se pasa un puntero al valor de la dirección de otro puntero, por lo que se puede modificar en el otro puntero que apunta a una función secundaria, como:

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

¡Estoy confundido! –

+0

lo siento, sé que fue bastante malo. Intenta leer, erm, esto: http://www.codeproject.com/KB/cpp/PtrToPtr.aspx –

11

Le recomendamos que lea esto: Pointers to Pointers

Espero que esto ayude a aclarar algunas dudas básicas.

6

Un puntero a un puntero también se denomina asa . Un uso para ello suele ser cuando un objeto se puede mover en la memoria o eliminar. Uno es a menudo responsable de bloquear y desbloquear el uso del objeto , por lo que no se moverá al acceder a él.

Se usa a menudo en entornos con memoria restringida, es decir, el sistema operativo Palm.

computer.howstuffworks.com Link>>

www.flippinbits.com Link>>

40

¿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 simplemente es un puntero.

¿Cuándo las usarías?

Puede usarlos cuando necesite devolver un puntero a alguna memoria en el montón, pero no utilizando 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 se llama 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 

Hay otros usos también, como el argumento principal() de cada programa en C tiene un puntero a un puntero de argv, donde cada elemento contiene una matriz de caracteres que son las opciones de línea de comando. Sin embargo, debe tener cuidado cuando utiliza punteros de punteros para apuntar a 2 matrices dimensionales, en su lugar, es mejor utilizar un puntero a una matriz bidimensional.

¿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í es un ejemplo de un puntero a una matriz de 2 dimensiones se hace correctamente:

int (*myPointerTo2DimArray)[ROWS][COLUMNS] 

No puede utilizar un puntero a una matriz de 2 dimensiones, aunque si quieres apoyar a un número variable de elementos para las FILAS y COLUMNAS. Pero cuando sabes de antemano usarías una matriz bidimensional.

5

Tiene una variable que contiene una dirección de algo. Eso es un puntero.

Luego tiene otra variable que contiene la dirección de la primera variable. Eso es un puntero al puntero.

317

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 se puede ver aquí, es que en la dirección 63 de la cadena "Hola" comienza. Así pues, en este caso, si esta es la única ocurrencia de "hola" en memoria a continuación,

const char *c = "hello"; 

... define c ser un puntero a la cadena (sólo lectura) "hola", y por lo tanto contiene el valor 63. c debe almacenarse en alguna parte: en el ejemplo anterior en la ubicación 58. Por supuesto, no solo podemos señalar caracteres, sino también otros punteros. Ej .:

const char **cp = &c; 

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

const char ***cpp = &cp; 

ahora cpp almacena la dirección de cp. Por lo tanto, tiene un valor de 55 (basado en el ejemplo anterior), y lo has adivinado: ella misma se almacena en la dirección 60.


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

  • El nombre de una matriz generalmente arroja la dirección de su primer elemento. Entonces, si la matriz contiene elementos del tipo t, una referencia a la matriz tiene el tipo t *.Ahora considere una matriz de matrices de tipo t: naturalmente, una referencia a esta matriz 2D tendrá el tipo (t *)* = t ** y, por lo tanto, es un puntero a un puntero.
  • Aunque una matriz de cadenas suena unidimensional, de hecho 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 se trata de alterar una variable del tipo t *.
  • Muchas otras razones que son demasiado numerosas para enumerarlas aquí.
+6

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

+2

Stephan hizo un buen trabajo reproduciendo, básicamente, el diagrama en The C Programming Language de Kernighan & Richie. Si está programando C, y no tiene este libro y está contento con 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. –

+3

char * c = "hola" debe ser const char * c = "hola".También es a lo más 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 arroja un puntero a su primer elemento, pero no siempre. Acerca de los punteros a los punteros, simplemente diría que son útiles cuando una función tiene que modificar un puntero pasado como parámetro (en su lugar, pasa un puntero al puntero). –

7

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

Si pasa un único puntero como argumento, modificará las copias locales del puntero, no el puntero original en el ámbito de la llamada. Con un puntero a un puntero, usted modifica el último.

+0

Bien explicado para la parte 'Por qué' –

13

Cuando cubrimos punteros en un curso de programación en la universidad, recibimos dos pistas sobre cómo comenzar a aprender sobre ellos. El primero era ver Pointer Fun With Binky. El segundo era que pensar en el Haddocks' Eyes paso de Lewis Carroll de A través del espejo

“Estás triste”, dijo el Caballero en un tono ansioso: “Deja que te cante una canción para consolarte. "

" ¿Es muy largo? "Preguntó Alice, porque ese día había escuchado mucha poesía.

"Es largo", dijo el Caballero, "pero es muy, muy hermoso. Todo el mundo que me oye cantarlo, o les saca lágrimas a los ojos, o bien, "

" ¿O bien qué? ", Dijo Alicia, porque el Caballero había hecho una pausa repentina.

"O bien, no es así, ya sabes. El nombre de la canción se llama Ojos 'Haddocks'. '”

“Oh, ese es el nombre de la canción, ¿verdad?" Alice dijo, tratando de sentir interés.

“No, usted no' "Entiendo", dijo el Caballero, luciendo un poco irritado. "Así es como se llama. El nombre es realmente 'El hombre de edad avanzada'"

"Entonces debería haber dicho 'Así es la canción llamado "?" Alice se corrigió a sí misma.

"No, no deberías: ¡eso es otra cosa! La canción se llama 'Formas y medios': pero eso es solo lo que se llama, ¡ya sabes! "

" Bueno, ¿qué es la canción, entonces? ", dijo Alicia, que en ese momento estaba completamente desconcertada.

"Estaba llegando a eso", dijo el Caballero. “La canción es realmente‘A-sentado en una puerta.’: Y la melodía de mi propia invención”

+1

Tuve que leer ese pasaje un par de veces ... +1 por hacerme pensar! –

+0

Es por eso que Lewis Carroll no es una persona común escritor. – metarose

25

me gusta este "mundo real" ejemplo de código de puntero a puntero uso, en Git 2.0, commit 7b1004b:

Linus dijo una vez:

De hecho, me gustaría que más gente comprendiera la realidad núcleo tipo de bajo nivel de codificación. Cosas grandes y complejas como la búsqueda de nombres sin cerrojo, pero simplemente un buen uso de punteros a punteros, etc.
Por ejemplo, he visto demasiadas personas que eliminan una entrada de lista enlazada de manera simple al realizar un seguimiento del "anterior "entrada, y luego para eliminar 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 eso, sólo tiene que ir 'Esta persona no entiende punteros' . Y es tristemente bastante común.

La gente que entiende punteros sólo tiene que utilizar un "puntero al puntero de entrada", e inicializar que con la dirección de la list_head. Y luego a medida que atraviesan la lista, se puede eliminar la entrada sin el uso de los condicionales, con sólo hacer un

*pp = entry->next 

http://i.stack.imgur.com/bpfxT.gif

Aplicando que la simplificación nos permite perder 7 líneas de esta función incluso mientras agrega 2 líneas de comentario.

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

Chris puntos fuera in the comments al 2016 de vídeo "Linus Torvalds's Double Pointer Problem" por Philip Buuck.


kumar puntos fuera in the comments la entrada de blog "Linus on Understanding Pointers", donde Grisha Trubetskoy explica:

Imagínese que usted ha definido una lista enlazada como:

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

Necesitas ite puntúalo desde el principio hasta el final y elimina un elemento específico cuyo valor sea igual al valor de to_remove.
La forma más obvia de hacerlo 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 anterior es:

  • iteración en la lista hasta la entrada es NULL, lo que significa que hemos llegó al final de la lista (línea 4).
  • Cuando nos encontramos con una entrada que desea eliminar (línea 5), ​​
    • asignamos el valor del puntero actual próxima a la anterior,
    • eliminando así el elemento actual (línea 7).

Hay un caso especial, más que - al comienzo de la iteración no hay una entrada anterior (prev es NULL), y por lo tanto para eliminar la primera entrada en la lista tiene que modificar propia cabeza (línea 9).

Lo que Linus decía 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.
continuación, el código es el siguiente:

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

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

    pp = &entry->next; 
    entry = entry->next; 
} 

El código anterior es muy similar a la variante anterior, pero note la forma en que ya no tenemos que mirar para el 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. No es ciertamente 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 desgraciadamente casi nada en C se debe asumir que es atómico (incluidos los operadores ++ y --)!

+4

Esto ayudará a comprender mejor - http://grisha.org/blog/2013/04/02/linus-on-understanding-pointers/ – kumar

+0

@kumar buena referencia. Lo he incluido en la respuesta para el mo Re visibilidad. – VonC

+0

[Este video] (https://www.youtube.com/watch?v=GiAhUYCUDVc) fue esencial para mí en la comprensión de su ejemplo. En particular, me sentía confundido (y beligerante) hasta que dibujé un diagrama de memoria y tracé el progreso del programa. Dicho eso, todavía me parece algo misterioso. – Chris

1

Cómo funciona: Es una variable que puede almacenar otro puntero.

Cuando los usarías: Muchos usos uno de ellos es si tu función quiere construir una matriz y devolverla a la persona que llama.

//returns the array of roll nos {11, 12} through paramater 
// return value is total number of students 
int fun(int **i) 
{ 
    int *j; 
    *i = (int*)malloc (2*sizeof(int)); 
    **i = 11; // e.g., newly allocated memory 0x2000 store 11 
    j = *i; 
    j++; 
    *j = 12; ; // e.g., newly allocated memory 0x2004 store 12 

    return 2; 
} 

int main() 
{ 
    int *i; 
    int n = fun(&i); // hey I don't know how many students are in your class please send all of their roll numbers. 
    for (int j=0; j<n; j++) 
     printf("roll no = %d \n", i[j]); 

    return 0; 
} 
3

considerar la figura de abajo y programa para entender mejor este concepto.

Double pointer diagram

Según la figura, ptr1 es una único puntero que está teniendo dirección de la variable num.

ptr1 = &num; 

mismo modo ptr2 es un puntero a puntero (doble puntero) que está teniendo la dirección de 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 desde arriba diagrama:

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

de salida:

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

Hay tantos o f las explicaciones útiles, pero no encontré solo una breve descripción, así que ...

Básicamente el puntero es la dirección de la variable. corto código Resumen:

 int a, *p_a;//declaration of normal variable and int pointer variable 
    a = 56;  //simply assign value 
    p_a = &a; //save address of "a" to pointer variable 
    *p_a = 15; //override the value of the variable 

//print 0xfoo and 15 
//- first is address, 2nd is value stored at this address (that is called dereference) 
    printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

también información útil se puede encontrar en el tema What means reference and dereference

Y no estoy tan seguro, cuando puede haber indicadores útiles, pero en común que es necesario su uso cuando se está haciendo un poco de manual/dynamic memory allocation- malloc, calloc, etc.

así que espero que también ayuda a aclarar la problemática :)

Cuestiones relacionadas