Punteros de memoria en C

Publicado el Martes, 25 de septiembre de 2007

El puntero de memoria es uno de los conceptos más difíciles, aparentemente, de entender. Antes de darlos en algoritmos, ya había leído al respecto en libros e internet, y además repasamos el concepto en la reunión de C++. Una definición formal sería algo así:

Un puntero es un tipo de dato o variable que contiene como valor una dirección de memoria.

O sea, en vez de ser una variable con un valor numérico como int, o float, guarda una dirección de memoria. Se le llama puntero porque apunta a la dirección de memoria que almacena.

Es importante diferenciar dos aspectos del puntero. Acá viene lo "difícil", ya que es un concepto bastante abstracto, y se presta a confusiones. El puntero como tipo de datos que es, ocupa su lugar en la memoria, y contiene una dirección de memoria. Si nos imaginamos que la memoria de la computadora es un organizador con varios espacios. Cada espacio tiene su dirección única. Si en un espacio guardamos un entero, en el cajón con dirección X tenemos el entero con valor Y. Un puntero puede por ejemplo guardar el valor de X, pero a su vez va a tener un valor distinto X1 que será su propia dirección en la memoria.

Resumiendo, es un tipo de variable que en vez de guardar un valor como "42" o "fernando", guarda una dirección de memoria que puede parecerse a algo así: 0xbff9a1e0
Esa dirección de memoria puede apuntar a un entero, caracter u otro tipo de dato. Un puntero ocupa 4 bytes en memoria en plataformas de 32 bits. Pero en las arquitecturas de 16 bits mide generalmente 2 bytes, y en las de 64 bits, mide 8 bytes (Gracias Francisco por el comentario)
Actualmente, en lenguajes como Java o Visual Basic, los punteros se manejan de forma invisible para el programador. Por ejemplo en Visual Basic, cuando en una función pasamos por parámetro una variable "ByRef" (por defecto se pasan "ByVal") estamos pasando un puntero.
Esto supone la ventaja para el programador que no tiene que preocuparse por los punteros y los problemas que trae con la memoria, y sí preocuparse por resolver el problema del algoritmo. Pero también le quita todas las ventajas de optimización que traen.

El problema con los lenguajes de alto nivel, es que ahora se crean los "programadores Express". Se les enseña algún lenguaje fácil orientado a objetos (léase VB) para tener salida laboral rápida, y se olvidan muchos conceptos abstractos importantes como punteros y estructuras de datos. En el caso de mi curso, pasa en los primeros dos semestres, donde se ve todo en Visual Basic.Net, cuando se llega al tercer semestre y vemos Java, la mayoría miran como si fuera japonés. Y ni hablar cuando llegamos a Algoritmos y estructuras de datos...

Pero eso da para otro post que ya tendrá su lugar... Siguiendo con los punteros:

EJEMPLOS EN C

Ejemplos punteros en C

El primer programa que hice fue direccion.c para entender un poco más el tema de direcciones de memoria. En mi caso al compilar y ejecutarlo, el resultado fue el valor de memoria que escribí más arriba. Ahora si lo vuelvo a ejecutar da: 0xbfb8ddd0
Viendo el código se nota que son solo dos líneas. Al declarar una variable, reservamos el espacio en memoria para almacenar su valor. Y para conocer la dirección de memoria que se le asignó a una variable, se usa el ampersand "&" como operador de dirección (printf("%p \n", &var);).

Si intentáramos guardar el valor de &var en una variable común, el compilador da un error, y si bien se puede correr el programa, el valor que guardamos no va a ser la dirección de memoria de la variable, sino "basura".
Por cierto, el error: warning: assignment makes integer from pointer without a cast parece bastante común cuando se trabaja con punteros. Significa que intentamos darle un valor puntero a una variable que no lo es.

Declarando un puntero

Declarar un puntero en C lleva el siguiente formato:
tipoDeDato *nombre;
Lo he visto de dos formas distintas, con el asterisco pegado al tipo de datos, y al nombre del puntero, y con gcc por lo menos, compila de ambas formas.

Actualizado: Gracias a los aportes en los comentarios de yiyus y Francisco, puedo agregar:
En C no importa cuántos espacios pongas ni donde siempre y cuando los tokens queden bien separados, por eso tipoDeDatos*nombre; es tan válido como: tipoDeDatos* nombre;
Sin embargo, por cuestión de estilo el asterisco * debería estar junto al nombre de la variable en vez de el tipo:
char *s, *t, *u;
En vez de:
char* s, t, u;
Lo cual es incorrecto, ya que ´t´ y ´u´no son declaradas como punteros. (citado de http://www.psgd.org/paul/docs/cstyle/cstyle04.htm)

El ejemplo siguiente declara una variable entera de nombre 'var' y un puntero de valor entero y nombre 'punt'. Apuntamos el puntero 'punt' a la dirección de memoria de 'var'. Le dí el valor 7 al entero, ya que en caso de no darle un valor, nos va a mostrar cualquier dato basura que hubiera en ese espacio de memoria al momento de declarar la variable.
Ahora, imprimimos en pantalla el valor de 'var' que es 7. Después se imprime su dirección de memoria con el especificador de formato "%p" (muestra valor puntero o dirección de memoria) y el ampersand &.
En la siguiente línea, se muestra el valor que guardó el puntero 'punt', con %p y 'punt', que podemos ver si compilamos y ejecutamos, que es el mismo valor que la dirección de memoria de 'var'. Por último, mostramos la dirección de memoria de 'punt'.

Escribiendo el código y compilando puede ir adquiriendo el concepto de puntero, dirección de memoria y las posibles confusiones. Éste post es solo a modo de ejemplo para ir agarrándole la mano al tema. Más adelante iré posteando sobre usos útiles de los punteros como enlazar estructuras de datos en estructuras dinámicas y pasar argumentos por referencia.

Por ahora eso, espero que a alguien le sirva. En lo personal me sirve mucho para practicar y dejar los conceptos más claros. Los fuentes fueron compilados usando gcc 4.1 en GNU/Debian Etch. Si encuentras algún error o quieres hacer algún comentario, crítica o aporte, por favor deja un comentario.
Hay mucho más para estudiar respecto a los punteros como dereferenciación, segmentation fault y demás golpes, pero por ahora lo dejamos acá.

12 comentarios en este post

Feed de comentarios
  1. Avatar

    Francisco Castro 25 septiembre. 2007 - 14:57

    Quiero agregarte algunas precisiones, un puntero no siempre mide 4 bytes, de hecho, en las arquitecturas de 16 bits mide generalmente 2 bytes, y en las de 64 bits, mide 8 bytes.

    Otro punto más, es que en C no importa cuántos espacios pongas ni donde siempre y cuando los tokens queden bien separados, por eso tipoDeDatos*nombre; es tan válido como:

    tipoDeDatos
    * nombre;

  2. Avatar

    fernando 25 septiembre. 2007 - 15:05

    Francisco:
    Gracias por tu comentario.
    Es verdad que no aclaré la diferencia en el valor en bytes de los punteros, como bien decís, depende de cada arquitectura.
    Y gracias por la aclaración en cuanto a los espacios. Lo voy a tener en cuenta.
    Por cierto, tengo tu blog en el FeedReader de hace tiempo, y me bajé el código fuente para estudiarlo.
    Para los que lean esto recomiendo visitar: http://fideo.no-ip.info/ un blog hecho enteramente en C!

  3. Avatar

    TheKeeperOfThe7Keys 25 septiembre. 2007 - 16:23

    Yo agregaría a los “programadores express” los usuarios de GeneXus que van por el mundo haciéndole creer a la gente que son programadores… y esos son aún peores que los programadores exress… los express por lo menos escriben algo de código.

  4. Avatar

    fernando 25 septiembre. 2007 - 16:38

    GeneXus es mala palabra…
    Aunque buen escribiste: “USUARIOS de GeneXus”, nótese la diferencia con “Programador”. Usuario y programador -> opuestos.
    Los Programadores Express usan bastante la fórmula:
    Google – Copiar – Pegar
    Aunque cada dos por tres tienen que declarar alguna variable a mano.

  5. Avatar

    Denis 27 septiembre. 2007 - 16:10

    Al autor del post original: ¿dónde estudiaste? ¿En la Ort? Yo igual, pero hice el curso con VB6, que lleva un poco más de programación manual que Net. Tenés razón, cada vez menos se sabe, o explica, cómo funciona un ordenador internamente, o un programa. Nunca lo aprendí a fondo, aunque me encantaría. Pero también debés reconocer que los lenguajes como Net permiten que el programador pueda dedicarse mucho más al diseño del sistema, requerimientos y cómo resolverlos e implementarlos, y no perder tanto tiempo en implementación y resolviendo errores de bajo nivel de código. De hecho, yo estoy haciendo un programa en Net (nuevo para mí, y sí, he tenido que recurrir mucho a la ayuda o al genio de Guille, no me queda otra) y mi mayor preocupación es cómo resolver el programa, hacer que sea fiable y usable, y no tanto en romperme la cabeza en errores de los cuales se ocupa la plataforma misma.

  6. Avatar

    fernando 27 septiembre. 2007 - 17:20

    Denis:
    Estoy estudiando en el CEI, ¿no fuimos compañeros el año pasado? Había un Denis en la clase pero dejó de ir a mitad de año más o menos.
    Con respecto a tu comentario, en el post mismo reconozco que los lenguajes de alto nivel suponen la ventaja que no hay que preocuparse por los temas de implementación a bajo nivel. Sin embargo el problema es cuando lo único que aprendes es eso. Si no tenés idea de cómo funcionan las cosas a más bajo nivel, no te preocupas por conocer cómo trabajan otros lenguajes, etc. no sos un buen programador. Y eso además de unas cuantas cosas más es una tendencia que hay en algunos programadores o estudiantes actualmente y ésto los hace lo que yo le puse el nombre “programadores express”.
    Obviamente recurrir a ayuda es muy útil, casi imprescindible, pero si yo encuentro la solución a un problema en Google, copio y lo pego en mi programa, no aprendí nada. Y si mañana tengo el mismo problema qué hago? Copio y pego de nuevo. Y qué aprendí? a copiar y pegar. No viene mal leer un poco de teórico para ver cómo funciona.
    Hay muchos fundamentos y conceptos que son imprescindibles tener en cuenta y muchos “programadores” no los saben, ni les interesa aprenderlos. Total, ¿para qué los van a usar si .Net lo hace solo? Pero y si mañana les piden que hagan un proyecto en Java, ¿van a decir que solo saben usar .NET? No solo se limitan a una plataforma de desarrollo sino a una plataforma de desarrollo dentro de un sistema operativo en una computadora. Ni hablemos de otros S.O., celulares, u otros dispositivos que precisen software. ¿Y esos se pueden hacer llamar “programadores”?
    Es un tema bastante extenso, a mí en lo personal me molesta bastante, pero bueno, de última cada maestro con su librito…
    Gracias por visitar la página y dar tu opinión.
    Saludos

  7. Avatar

    TheKeeperOfThe7Keys 28 septiembre. 2007 - 12:12

    Todo ese tema me recuerda a este post que leí hace un tiempo.

    En lo personal creo que hay personas que pueden programar y hay otras que creen que pueden… cuando a mi me preguntan si estudiar A/P o alguna carrera similar siempre les digo que es una buena carrera, pero te tiene que gustar, sino no vas a llegar a nada o vas a ser de esos del montón, los express o seguramente termine uniéndose a esa masa de usuarios de GeneXus que se hacen llamar programadores… y he ahí un concepto que nunca voy a entender… ¿por qué un programador (de verdad) usaría un generador de código?

  8. Avatar

    yiyus 2 octubre. 2007 - 09:21

    Aunque es semanticamente correcto, por cuestión de estilo se recomienda poner el asterisco con la variable. Citando http://www.psgd.org/paul/docs/cstyle/cstyle04.htm :

    The “pointer” qualifier, ‘*’, should be with the variable name rather than with the type.

    char *s, *t, *u;

    instead of

    char* s, t, u;

    which is wrong, since ‘t’ and ‘u’ do not get declared as pointers.

    Si quieres aprender C de verdad te recomiendo leer guías de estilo y libros como “The art of UNIX programming” de Eric. S. Raymond y “The practice of programming” de Kernighan y Rob Pike.
    Suerte con la carrera.

  9. Avatar

    fernando 2 octubre. 2007 - 11:58

    yiyus:
    Muchas gracias por tu aporte. Al ver un ejemplo como ese, queda bien claro.
    Voy a bajarme el de Eric Raymond que está disponible en su web, y ver de buscar el otro.
    Te agradezco nuevamente y espero contar con más aportes de este tipo.
    Saludos

  10. Avatar

    Ricardo 13 diciembre. 2007 - 00:43

    Bastante curioso hoy encontre tu sitio mientras trataba de olvidar un poco la escuela… estoy estudiando ing. en sistemas computacionales y en el instituto politecnico nacional de México es completamente distinta la forma de aprender quisiera me dieras tu opinion yo primero veo c y c++ y despues programación grafica a mi por el momento me fascina c ojala pudieras responder

  11. Avatar

    fernando 13 diciembre. 2007 - 10:37

    Ricardo:
    A mí forma de ver, la forma en que te enseñan a ti es la mejor. Porque primero te enseñan C que es bien a bajo nivel, y entiendes cómo maneja la memoria la máquina, y un montón de cosas teóricas que después no usas en lenguajes de más alto nivel. A mí también me encanta programar en C, aunque no le pueda dedicar mucho tiempo.
    La forma en que me enseñaron a mí es la que está errada. Empiezas aprendiendo lenguajes de alto nivel, que “hacen todo solo”, como las estructuras de datos dinámicas que ya están implementadas, y ni que hablar de los punteros que no existen (salvo en Java, que existen pero no se puede usar aritmética de punteros con ellos). Ese es sólo mi opinión, creo que si estás empezando a aprender con C, vas por buen camino.
    Gracias por visitar el blog y dejar tu comentario.
    Saludos

  1. programame.net | 25 septiembre. 2007 - 12:41

    Punteros de memoria en C

    El puntero de memoria es uno de los conceptos más difíciles, aparentemente, de entender. A continuación su definición y ejemplos en C para practicar y adquirir bien el concepto. Código fuente disponible.

Dejar un comentario

Toasty!