viernes, 26 de marzo de 2010

Sobre punteros c

Empty your memory,
with a free()…
like a pointer!

If you cast a pointer to a integer,
it becomes the integer,
if you cast a pointer to a struct,
it becomes the struct…
The pointer can crash…,
and can Overflow…
Be a pointer my friend…

¿Quién no ha tenido nunca más de un quebradero de cabeza con una violación de segmento, o un core? A veces te vale la excusa de "es que en casa sí funciona...", pero créeme, en la mayoría de los casos no.

Trabajar con punteros es relativamente sencillo si tienes una idea clara de cómo referenciar a memoria y si sigues una metodología sistemática a la hora de hacerlo.
Intenta entender los siguientes aspectos, es posible que una vez entendidos disminuyas considerablemente el número de cores o fallos provocados por accesos a memoria:
  1. A la hora de realizar alguna comparación, ten cuidado cuando las variables implicadas sean punteros, es posible que no quieras comparar el VALOR DEL PUNTERO (es decir, la dirección de memoria) y que lo que quieras comparar realmente es el VALOR al que apunta el puntero (es decir, el valor contenido en memoria).
  2. Asegurate de que liberas los punteros para evitar fugas de memoria (o memory leaks). Esto es un problema sobretodo en proyectos grandes. Imagínate que en tu programa realizas una llamada a una función que reserva 1 KB, que olvidas liberar. Esto no es problema si tu programa terminase, pero si actua como demonio, en segundo plano... y además no realiza esa llamada una vez, sino varias, tu programa estaría consumiendo de forma innecesaria memoria del sistema de forma incremental.
  3. De igual modo que en 1.-, ten cuidado a la hora de asignar valores a punteros. Tenemos una tendencia a copiar un puntero en otro puntero con el operador igual. Esto solo es válido si lo que quieres copiar son las referencias a memoria. Si no, deberías copiar elemento a elemento o mediante un memcpy...
  4. A la hora de trabajar con paso por referencia, es más comodo reservar la variable que vas a pasar por referencia de forma estática y en la llamada al procedimiento/función pasarle la dirección con el operador &.
  5. Recuerda que si reservas memoria dentro de un procedimiento/función la estás reservando en pila, por lo que cuando llames a otras 1 o 2 funciones más se machacará, probocando un core. Si quieres reservar memoria dentro de un procedimiento para una variable que le pasas por referencia debes pasar el puntero por referencia, y no la variable. Es decir, la signatura del parámetro debe ser "puntero a puntero" y no "puntero"
  6. Ten en cuenta que la mayoría de las veces que declaras un puntero debes reservar memoria, a no ser que lo vayas a usar como un puntero auxiliar o algo por el estilo.
  7. Presta especial atención a no acceder a posiciones del puntero que sobrepasen lo que has reservado para el. Recuerda siempre que debes indexar entre 0 y TAMAÑO-1.
Veamos un ejemplo del caso 1.-




















Este ejemplo está testeado en un ubuntu 9.10.
Los resultados son los esperados:
  • La primera comparación, en la que comparamos los punteros, nos dice que no son iguales.
  • La segunda, en la que comparamos el contenido de cada posición, nos dice que sí.
Si te fijas en cómo comparo elemento a elemento, sumo -i- al puntero. Hubiera dado lo mismo si lo hubiese hecho con -[i]-, pero es por diferenciar un poco entre memoria estática y dinámica.
Efectos del caso 2.-

Veamos un ejemplo de como producir una fuga de forma "involuntaria".











Podemos observar en el código que la memoria reservada en foo() no se libera, y que se llama a dicha función en el código de forma indefinida mediante un while(1), por lo que si este proceso se ejecuta en segundo plano provocará un incremento del uso de memoria a medida que pase el tiempo.

Una forma de monitorizar el uso de memoria en linux es a través de ps aux, observando la columna stat:








Efectos del caso 3.-

En rojo encuadro el error habitual y en azul una forma correcta de hacerlo.




















La parte comentada sería la forma incorrecta de hacerlo. Está así porque da un core. Los printfs están colocados a posta para que se machaque la pila, si no estuvieran es posible que no se pudiera reproducir el core.
Si te fijas, al llamar a foo2, el parámetro viene precedido del símbolo &. Esto significa que es la dirección de esa variable que le pasamos como parámetro, que sería la dirección del puntero, es decir, un puntero a puntero, cumpliendo con la signatura de la cabecera de la función.
Esto es todo de momento...
SALUDOS!

contador de visitas

Vuelos Baratos
Anuns
Blogs
blogs

No hay comentarios:

Publicar un comentario