¿Qué hace ** en lenguaje C? [duplicate]

12 minutos de lectura

avatar de usuario
Jaime

Soy nuevo en C con una buena experiencia en Java y estoy tratando de entender los punteros y las matrices.

Conozco ese subíndice operator[] es parte de una definición de matriz, entonces:

int numbers[] = {1,3,4,5};

crearía una matriz de enteros, que se representaría en la memoria como 16 bytes, 4 lotes de 4 bytes:

numbers[0] = 1, address 0061FF1C
numbers[1] = 3, address 0061FF20
numbers[2] = 4, address 0061FF24
numbers[3] = 5, address 0061FF28

Sin embargo, cuando se trata de punteros, mi conocimiento comienza a fallar, por lo que si tuviera que crear un puntero a los números de la matriz, haría lo siguiente:

int *pNumbers = &numbers[0];

que se vería algo como esto:

puntero a números

¿Y supongo que sería de tamaño 4 bytes?

Sin embargo, el ** Leí como “puntero a un puntero”, lo que no tiene sentido para mí, ¿por qué alguien querría un puntero a un puntero, seguramente si a->b->c entonces a->c sería suficiente? Sé que me estoy perdiendo algo, y debe tener algo que ver con matrices como argv puede ser de tipo char[ ] o char ** como se ve a continuación:

int main(int argc, char **argv){}

Asi que:

  • Que es esto (**)?
  • que utilidad tiene
  • ¿Cómo se representa en la memoria?

  • Querría un puntero a un puntero por la misma razón por la que querría un puntero a cualquier cosa. Por ejemplo, para modificar el puntero al que apunta.

    – usuario2357112

    26 de enero de 2016 a las 18:53

  • Los punteros a punteros tienen mucho sentido y se usan todo el tiempo. También, int no siempre es de 4 bytes. En algunas máquinas lo es. En máquinas de 64 bits, probablemente sea de 8 bytes. En algunos microprocesadores es de 2 bytes. El tamaño de un int es sizeof (int). Eso siempre será cierto.

    – Tom Karzes

    26/01/2016 a las 18:55

  • @Marged Pregunté qué usos tiene, las otras dos preguntas insinué que ya las sabía, pero no estaba 100% seguro, por eso pregunté explícitamente. La respuesta que elegí ilustra bien cómo se pueden usar estos punteros, mientras que la respuesta de la pregunta que etiquetó ilustra cómo funcionan los operadores * y & como ya sé en función de lo que he preguntado en la pregunta, consulte “int *pNumbers = &numbers[0];”

    – Jaime

    26 de enero de 2016 a las 19:23

  • @BenVoigt Creo que las respuestas a esta pregunta me ayudaron a darme cuenta de que mi problema es más el modelo de memoria que los punteros de los punteros. En Java, solo tenía una regla de que puedes manipular objetos, pero no primitivos. Eso parecía funcionar para mí hasta ahora y he estado programando durante casi dos años, pero ahora estoy empezando a cuestionar mi conocimiento del modelo de memoria. A menos que pueda decir que C solo tiene primitivos y los punteros le permiten ampliar el alcance de un primitivo al pasar un valor que hace referencia al primitivo original. O estoy siendo completamente estúpido. (que es totalmente una posibilidad)

    – Jaime

    26 de enero de 2016 a las 21:04

  • @immibis fue mi comprensión del “paso por valor” y paso por referencia “Lo entiendo ahora :). ¡Gracias!

    – Jaime

    26 de enero de 2016 a las 22:26

avatar de usuario
Vlad de Moscú

En C los argumentos se pasan por valores. Por ejemplo, si tiene una variable entera en main

int main( void )
{
    int x = 10;
    //...

y la siguiente funcion

void f( int x )
{
    x = 20;
    printf( "x = %d\n", x );
} 

entonces si llamas a la función en main así

f( x );

entonces el parámetro obtiene el valor de la variable x en principal Sin embargo, el parámetro en sí ocupa una extensión diferente en la memoria que el argumento. Entonces, cualquier cambio del parámetro en la función no influye en la variable original en main porque estos cambios ocurren en diferentes extensiones de memoria.

Entonces, ¿cómo cambiar la variable en main en la función?

Debe pasar una referencia a la variable usando punteros.

En este caso, la declaración de la función se verá como

void f( int *px );

y la definición de la función será

void f( int *px )
{
    *px = 20;
    printf( "*px = %d\n", *px );
} 

En este caso es la extensión de memoria ocupada por la variable original x se cambia porque dentro de la función tenemos acceso a esta medida usando el puntero

    *px = 20;

Naturalmente, la función debe llamarse en main como

f( &x );

Tenga en cuenta que el parámetro en sí que es el puntero px es como siempre una variable local de la función. Esa es la función que crea esta variable y la inicializa con la dirección de la variable x.

Ahora supongamos que en main declaró un puntero, por ejemplo, de la siguiente manera

int main( void )
{
   int *px = malloc( sizeof( int ) );
   //..

Y la función definida como

void f( int *px )
{
    px = malloc( sizeof( int ) );

    printf( "px = %p\n", px );
}

como parámetro px es una variable local asignándole cualquier valor que no influya en el puntero original. La función cambia una extensión de memoria diferente a la ocupada por el puntero original px en principal

¿Cómo cambiar el puntero original en la función? ¡Solo pásalo por referencia!

Por ejemplo

f( &px );
//...

void f( int **px )
{
    *px = malloc( sizeof( int ) );

    printf( "*px = %p\n", *px );
}

En este caso, el valor almacenado en el puntero original se cambiará dentro de la función porque la función que usa la desreferenciación accede a la misma extensión de memoria donde se definió el puntero original.

  • Entonces, ¿el puntero a puntero (**) está almacenando la dirección de memoria de otro puntero?

    – Julio Marko

    28 de mayo de 2017 a las 9:17

  • @JúliusMarko Sí, tienes razón. El valor de un puntero a puntero es una dirección de otro puntero.

    – Vlad de Moscú

    28 de mayo de 2017 a las 11:01

  • char** argv se puede considerar como una matriz de una matriz de chars.” No, los punteros no son matrices. char** es puntero a puntero a char. Eso es todo.

    – alk

    16 de abril de 2019 a las 17:01

  • @alk Nunca dije que un puntero ES una matriz, dije “un puntero se puede CONSIDERAR como una matriz”, ya que desde la perspectiva del usuario se puede interactuar con ellos de manera similar, principalmente a través del operador de subíndice. La razón por la que hice la comparación es porque los recién llegados que intentan comprender los punteros a menudo primero comprenden los arreglos, lo cual es útil debido a las similitudes.

    – kirk roerig

    15 de julio de 2021 a las 15:20


avatar de usuario
Jaime

Creo que voy a agregar mi propia respuesta aquí, ya que todos han hecho un trabajo increíble, pero estaba realmente confundido sobre cuál era el punto de un puntero a un puntero. La razón por la que se me ocurrió esto es porque tenía la impresión de que todos los valores, excepto los punteros, se pasaban por valor y los punteros se pasaban por referencia. Ver lo siguiente:

void f(int *x){
    printf("x: %d\n", *x);
    (*x)++;
}

void main(){
   int x = 5;
   int *px = &x;
   f(px);
   printf("x: %d",x);
}

produciría:

x: 5
x: 6

Esto me hizo pensar (por alguna razón) que los punteros se pasaban por referencia a medida que pasamos el puntero, lo manipulamos y luego lo separamos e imprimimos el nuevo valor. Si puede manipular un puntero en una función… ¡para empezar, tener un puntero a un puntero para poder manipular el puntero!

Esto me pareció incorrecto, y con razón porque sería una tontería tener un puntero para manipular un puntero cuando ya puedes manipular un puntero en una función. Sin embargo, la cosa con C; es todo se pasa por valor, incluso punteros. Permítanme explicar más usando algunos pseudo valores en lugar de las direcciones.

//this generates a new pointer to point to the address so lets give the
//new pointer the address 0061FF28, which has the value 0061FF1C.
void f(int 0061FF1C){
    // this prints out the value stored at 0061FF1C which is 5
    printf("x: %d\n", 5);
    // this FIRST gets the value stored at 0061FF1C which is 5
    // then increments it so thus 6 is now stored at 0061FF1C
    (5)++;
}

void main(){
   int x = 5;

   // this is an assumed address for x
   int *px = 0061FF1C;

   /*so far px is a pointer with the address lets say 0061FF24 which holds
    *the value 0061FF1C, when passing px to f we are passing by value...
    *thus 0061FF1C is passed in (NOT THE POINTER BUT THE VALUE IT HOLDS!)
    */

   f(px);

   /*this prints out the value stored at the address of x (0061FF1C) 
    *which is now 6
    */
   printf("x: %d",6);
}

Mi principal malentendido de punteros a punteros es el paso por valor frente al paso por referencia. El puntero original no se pasó a la función en absoluto, por lo que no podemos cambiar la dirección a la que apunta, solo la dirección del nuevo puntero (que tiene la ilusión de ser el antiguo puntero, ya que apunta a la dirección a la que apuntaba el antiguo puntero). apuntando a!).

avatar de usuario
Comunidad

La forma tradicional de escribir el argv el argumento es char *argv[] lo que da más información sobre lo que es, una matriz de punteros a caracteres (es decir, una matriz de cadenas).

Sin embargo, al pasar una matriz a una función, se convierte en un puntero, dejándote con un puntero a puntero a charo char **.


Por supuesto, los asteriscos dobles también se pueden usar cuando se elimina la referencia de un puntero a un puntero, por lo que sin el contexto agregado al final de la pregunta, hay dos respuestas a la pregunta qué ** significa en C, dependiendo del contexto.

Para continuar con el argv ejemplo, una forma de obtener el primer carácter del primer elemento en argv sería hacer argv[0][0], o podría usar el operador de desreferencia dos veces, como en **argv.

La indexación y desreferenciación de matrices es intercambiable en la mayoría de los lugares, porque para cualquier puntero o formación p e índice i la expresion p[i] es equivalente a *(p + i). Y si i es 0 entonces nosotros tenemos *(p + 0) que se puede acortar a *(p) que es lo mismo que *p.

Como curiosidad, porque p[i] es equivalente a *(p + i) y el propiedad conmutativa de suma, la expresión *(p + i) es igual a *(i + p) lo que lleva a p[i] siendo igual a i[p].


Finalmente, una advertencia sobre el uso excesivo de punteros, es posible que en algún momento escuche la frase programador de tres estrellasque es cuando uno usa tres asteriscos como en *** (como en un puntero a un puntero a un puntero). Pero para citar del enlace.

Solo para ser claros: ser llamado ThreeStarProgrammer generalmente no es un cumplido

Y otra advertencia: una matriz de matrices es no lo mismo que un puntero a un puntero (enlace a una respuesta mía anterior, que también muestra el diseño de memoria de un puntero a un puntero como sustituto de una matriz de matrices).

avatar de usuario
Comunidad

** significa puntero a puntero como conoce el nombre. Te explico cada una de tus preguntas:

Que es esto (**)?

Puntero a puntero. A veces la gente llama doble puntero. Por ejemplo:

int a = 3;
int* b = &a; // b is pointer. stored address of a
int**b = &b;  // c is pointer to pointer. stored address of b
int***d = &c; // d is pointer to pointer to pointer. stored address of d. You get it. 

¿Cómo se representa en la memoria?

c en el ejemplo anterior es solo una variable normal y tiene la misma representación que otras variables (puntero, int …). Tamaño de memoria de la variable c igual que b y depende de la plataforma. Por ejemplo, una computadora de 32 bits, cada dirección variable incluye 32 bits, por lo que el tamaño será de 4 bytes (8×4 = 32 bits). En una computadora de 64 bits, cada dirección variable será de 64 bits, por lo que el tamaño será de 8 bytes (8×8 = 64 bits).

que utilidad tiene

Hay muchos usos para puntero a puntero, depende de su situación. Por ejemplo, aquí hay un ejemplo que aprendí en mi clase de algoritmo. Tienes una lista enlazada. Ahora, desea escribir un método para cambiar esa lista vinculada, y su método puede cambiar el encabezado de la lista vinculada. (Ejemplo: eliminar un elemento con valor igual a 5, eliminar el elemento principal, intercambiar, …). Así que tienes dos casos:

1. Si solo pasa un puntero del elemento de cabeza. Tal vez ese elemento principal se elimine y este puntero ya no sea válido.

2. Si pasa el puntero del puntero del elemento principal. En caso de que se elimine el elemento principal, no encontrará ningún problema porque el puntero del puntero todavía está allí. Simplemente cambia los valores de otro nodo principal.

Puede hacer referencia aquí para el ejemplo anterior: puntero a puntero en la lista vinculada

Otro uso es el uso en una matriz bidimensional. C es diferente de Java. Matriz bidimensional en C, de hecho, solo un bloque de memoria continuo. La matriz bidimensional en Java es un bloque de memoria múltiple (depende de su fila de matriz)

Espero que esto ayude 🙂

  • quieres decir: intc = &b; // c es puntero a puntero. dirección almacenada de b int*d = &c; // d es puntero a puntero a puntero. dirección almacenada de c. Usted lo consigue.

    – J.Doe

    20 de septiembre de 2020 a las 20:13


¿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