Paso por referencia de un arreglo malloc-eado en otra función

Si, el título es bastante raro y no quisé explayarme más. El otro día un amigo me presento este problema : “Si tengo un puntero int en una función, como puedo hacer para que en otra función se le asigne un espacio con malloc(), asignarle valores a ese espacio malloc-eado, y de retorno en la función inicial, poder imprimir estos datos ?” Un poco de pseudocódigo para que entiendan mejor :

funcion(puntero)
{
puntero = malloc(...)
for(...)
puntero[i] = i;
}

inicio()
{
int *ptr;
funcion(ptr);
printf(ptr[i]);
}

Le sugerí que pasará el puntero por referencia (&ptr), probé el código para el primer elemento del nuevo arreglo y funcionó. Después me dijo que no funcionaba para el segundo elemento y todos los demás. Y ahí se cayó el mundo. El código era el siguiente :

#include
#include

void modify(int **temp)
{
int i;
*temp = malloc(4*3);
for(i = 0; i < 3; i++)
*temp[i] = i;
}
int main()
{
int *arreglo;
modify(&arreglo);
printf("%d %d %d\n", arreglo[0] ,arreglo[1], arreglo[2]);
}

Este código, yo creía que perfecto, lanzaba este output.

$ ./malloc
0 0 0

Ante la duda, consulte en uno de los canales de IRC que frecuento, y les presente el problema y en un minuto me dijeron la solución : ” en vez de *temp[i] = i, usa (*temp)[i] = i . El operador ( ) tiene mayor precedencia que []“. Cambie esa línea y funcionó altiro, pero me costo entender un poco la solución.

Explicación

Todo se trata de la precedencia de operadores en C. Revisando cualquier tabla, tal como en esta página, vemos que () tiene mayor precedencia que [], y más abajo sale *. En resumen, () > [] > *. Ahora vamos a entender un poco de lo teórico y terminamos con lo práctico.

Al inicio, con *temp[i], lo que se produce es lo siguiente. Con i=0, *temp[0] = *(*temp), con lo cual se evalua (*temp) y luego el puntero a esa evaluación se le asigna 0, lo cual es correcto y por eso funciono cuando probé con solo un elemento. Pero después con el segundo elemento, el output tira 0 siendo que debería ser 1.

Por qué? Porque el operador [] tiene mayor precedencia que el operador de indirección *, con lo cual primero se evalua temp[i], y luego se desreferencia, con lo cual muestra elementos cercanos a la variable temp y no a lo que apunta *temp. Si bien temp es un puntero al puntero arreglo, ésta tiene una dirección propia (&temp).

Por el contrario, si usamos (*temp)[i], primero se desreferencia el puntero *temp, y luego se mueve el offset i desde la direccion a la que apunta *temp.

Este código pretende dejar más claro el uso de los operadores en las variables:

 #include
#include

void modify(int **temp)
{
int i;
*temp = malloc(4*3);

printf("temp @ %p\n", &temp);
printf("temp apunta a @ %p\n", *temp);

printf("\nMetodo *temp[i]:\n");
for(i = 0; i < 3; i++)
printf("*temp[%d] : %p\n", i, temp[i]);

printf("\nMetodo (*temp)[i]\n");
for(i = 0; i < 3; i++)
{
printf("(*temp)[%d] : %p\n", i, &(*temp)[i]);
(*temp)[i] = i;
}
}
int main()
{
int *arreglo;
modify(&arreglo);
printf("\nRetornando en main()\n");
printf("arreglo[0] @ %p = %d\n", &arreglo[0], arreglo[0]);
printf("arreglo[1] @ %p = %d\n", &arreglo[1], arreglo[1]);
printf("arreglo[2] @ %p = %d\n", &arreglo[2], arreglo[2]);
}

El output de este programa es :

$ ./malloc-exp
temp @ 0xbfd7b870
temp apunta a @ 0x95ac008

Metodo *temp[i]:
*temp[0] : 0x95ac008
*temp[1] : 0xbfd7b8b0
*temp[2] : 0xbfd7b908

Metodo (*temp)[i]
(*temp)[0] : 0x95ac008
(*temp)[1] : 0x95ac00c
(*temp)[2] : 0x95ac010

Retornando en main()
arreglo[0] @ 0x95ac008 = 0
arreglo[1] @ 0x95ac00c = 1
arreglo[2] @ 0x95ac010 = 2

Ahora espero quede más clara la cosa. Si usamos *temp[i], para el primer elemento funciona (como lo vi antes), pero desde el segundo elemento las direcciones que toma son cercanas a la dirección de la variable temp (0xbfd7b870) y en offset mayores a los que supone la variable i (4 bytes). Al parecer esta tomando memoria que no está asignada. En cambio con (*temp)[i], todas las direcciones están juntas, tal como corresponde en un arreglo, separadas por 4 bytes que corresponde al tamaño de un entero.

Después de vuelta en main(), vemos que el arreglo apunta a las direcciones malloc-eadas anteriormente, y podemos acceder a los valores que antes fijamos.

Espero que este post sea de ayuda cuando tengan un problema de este tipo, es un poco difícil de explicar en keywords, pero bueno ya está. Una buena lección sobre operadores.

Saludos

This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

3 Responses to Paso por referencia de un arreglo malloc-eado en otra función

  1. RNT says:

    Estas cosas se las deberian explicar a los niños de Estructura de Datos!!!

  2. jareyes says:

    Hola Claudio, llegué a tu blog porque entré a pinguinux.cl por curiosear, hace años entraba a ese canal en IRC. También estudio Informática en la UTFSM, quizas nos encontremos por ahi.
    Buen artículo.
    Saludos!

Deja un comentario

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>