Catto / Mapa de Temas · Informática II 2do nivel
Informática II · 120 h · Contenido 1 de 8

Programación avanzada en C

En C se programa sabiendo dónde están las cosas. El puntero no es una rareza del lenguaje: es la dirección de memoria, y entenderlo es la diferencia entre pelearse con el compilador y manejar el hardware.

Punteros Direcciones Arreglos malloc Modularización

01Toda variable tiene una dirección

Una variable es un nombre para un lugar de la memoria. Ese lugar tiene una dirección, y C permite trabajar con ella directamente, algo que la mayoría de los lenguajes esconde.

OperadorSe leeQué devuelve
&xdirección de xDónde está guardada x
*pcontenido de pEl valor que hay en la dirección que guarda p
int *p;p es puntero a intDeclara una variable que guarda direcciones
Un puntero es una variable más

Ocupa lugar en memoria, tiene su propia dirección y se le puede asignar un valor. Lo único especial es que ese valor es una dirección, y que el tipo dice cuánto mide lo apuntado: eso es lo que hace que p++ avance 4 bytes en un int* y 1 byte en un char*.

Laboratorio · la memoria, paso a paso

Recorré el programa con el deslizador y mirá qué pasa en la memoria en cada línea.

02Arreglos, cadenas y punteros

En C el nombre de un arreglo es la dirección de su primer elemento. Por eso estas cuatro expresiones significan lo mismo:

v[i]  ≡  *(v + i)  ≡  *(i + v)  ≡  i[v]

La última es una curiosidad que conviene no usar, pero muestra hasta qué punto los corchetes son sólo una notación cómoda para la aritmética de punteros.

El arreglo no sabe cuánto mide

C no verifica los límites. Escribir en v[10] de un arreglo de 5 compila sin protestar y pisa lo que haya al lado: otra variable, la dirección de retorno de la función, cualquier cosa. Esa es la causa de la mitad de las fallas de seguridad de la historia del software, y por eso hay que pasar siempre el tamaño junto con el puntero.

Una cadena es un arreglo de char terminado en el byte cero. Ese cero no es decorativo: es lo único que le dice a strlen o a printf dónde termina el texto.

03Pasar por valor y por referencia

C pasa siempre los argumentos por valor: la función recibe una copia. Si hay que modificar algo de quien llama, se le pasa su dirección:

void duplicar(int *x) { *x = *x * 2; }

duplicar(&a); /* ahora a vale el doble */

Los arreglos parecen una excepción, pero no lo son: lo que se pasa es la dirección del primer elemento, así que la función puede modificar el contenido. Por eso void f(int v[]) y void f(int *v) son exactamente lo mismo para el compilador.

Punteros a función

También se puede guardar la dirección de una función y llamarla a través de ella. Con eso se arman tablas de despacho, callbacks y máquinas de estados, y es el mecanismo que usan los vectores de interrupción de un microcontrolador.

04Memoria dinámica

ZonaQuién la manejaVive hasta
Variables globales y estáticasEl compiladorQue termine el programa
Pila (variables locales)AutomáticoQue retorne la función
Montículo (malloc)El programadorQue alguien llame a free
int *v = malloc(n * sizeof(int));
if (v == NULL) { /* no había memoria */ }

free(v);
v = NULL;

Las tres reglas que evitan casi todos los problemas: verificar siempre que malloc no devolvió NULL, liberar exactamente una vez lo que se reservó, y poner el puntero en NULL después de liberarlo para que un uso posterior falle de inmediato en lugar de corromper memoria en silencio.

Los cuatro errores clásicos
  • Pérdida de memoria: reservar y no liberar. El programa crece hasta que el sistema lo mata.
  • Puntero colgante: usar memoria ya liberada.
  • Doble liberación: llamar dos veces a free con el mismo puntero.
  • Puntero no inicializado: usar un puntero que nunca recibió una dirección válida.

En un equipo embebido que corre meses sin reiniciarse, una pérdida de memoria de pocos bytes por hora termina en una falla a las semanas. Por eso muchos proyectos embebidos directamente prohíben malloc después del arranque.

05Modularizar

Un programa serio no vive en un solo archivo. El criterio es separar interfaz de implementación:

  • sensor.h: qué se puede usar —prototipos, tipos y constantes—, protegido contra inclusión múltiple con #ifndef SENSOR_H.
  • sensor.c: cómo está hecho. Lo que no se declara en el encabezado se marca static y queda privado del archivo.
  • Cada .c se compila por separado y el enlazador junta los objetos.

Esa organización es la que permite reemplazar el módulo de un sensor por otro sin tocar el resto, y es la base de las bibliotecas que se usan en la construcción del software.

06En el laboratorio

Actividad 1 · Ver las direcciones de verdad

Escribir un programa que imprima con %p las direcciones de una variable global, una local, una reservada con malloc y una constante de texto. Ordenarlas y deducir el mapa de memoria del proceso.

Actividad 2 · Implementar strlen y strcpy

Escribirlas usando sólo punteros, sin corchetes. Comparar con las de la biblioteca estándar y medir tiempos con cadenas largas.

Actividad 3 · Cazar una pérdida de memoria

Compilar un programa con un malloc sin free y correrlo bajo valgrind. Interpretar el informe, corregir y volver a verificar que no queda memoria perdida.

07Errores frecuentes

  • Confundir *p con p. Uno es el contenido; el otro, la dirección.
  • Devolver la dirección de una variable local. Al retornar la función, esa memoria ya no es válida.
  • Olvidar el byte cero al construir una cadena a mano.
  • Reservar con el tamaño equivocado: malloc(n) en vez de malloc(n * sizeof(int)).
  • Comparar cadenas con ==, que compara direcciones y no contenido: hay que usar strcmp.
  • Usar sizeof sobre un puntero esperando el tamaño del arreglo: da el tamaño del puntero.

08Autoevaluación

Si int v[5] empieza en 0x1000, ¿qué dirección es v + 2?

0x1008: la aritmética de punteros avanza de a sizeof(int), es decir 4 bytes por elemento.

¿Qué diferencia hay entre char s[] = "hola" y char *s = "hola"?

El primero es un arreglo de 5 bytes modificable, copia del texto. El segundo es un puntero a una constante de sólo lectura: escribir en ella es comportamiento indefinido.

¿Por qué void f(int x) no puede modificar la variable de quien la llama?

Porque recibe una copia. Hay que pasar la dirección: void f(int *x).

¿Qué pasa si se llama dos veces a free con el mismo puntero?

Comportamiento indefinido: en general corrompe las estructuras internas del asignador. Poner el puntero en NULL después de liberarlo lo evita, porque free(NULL) es seguro.

¿Cuánto vale sizeof(v) dentro de void f(int v[])?

El tamaño de un puntero —4 u 8 bytes—, no el del arreglo. El tamaño hay que pasarlo como parámetro aparte.

¿Para qué sirve declarar una función static en un archivo .c?

Para que sea privada de ese archivo y no aparezca en el enlazado: evita choques de nombres y aclara qué es interfaz y qué es implementación.

09Para ampliar

  • Brian W. Kernighan y Dennis M. Ritchie. El lenguaje de programación C. 2.ª ed., Pearson, 1991. El capítulo 5, sobre punteros y arreglos, sigue siendo la mejor explicación escrita del tema.
  • Randal E. Bryant y David R. O'Hallaron. Computer Systems: A Programmer's Perspective. 3.ª ed., Pearson, 2015. Qué pasa realmente en la memoria cuando se ejecuta un programa en C.
  • Peter van der Linden. Expert C Programming: Deep C Secrets. Prentice Hall, 1994. Las trampas del lenguaje explicadas con humor y con casos reales.
  • Herbert Schildt. C: manual de referencia. 4.ª ed., McGraw-Hill, 2001. Referencia rápida de biblioteca estándar, útil para tener al lado del teclado.
Desarrollo del contenido «Programación avanzada en C» de Informática II (segundo nivel), según el diseño curricular de Ingeniería Electrónica, Plan 2023 — Ordenanza N° 1849 del Consejo Superior de la UTN. Volver al Mapa de Temas · catto.ar