Tema 5 · Punters i memòria dinàmica
Introducció
Un punter és una variable que emmagatzema adreces de memòria. Ens permet treballar amb dades de manera indirecta i gestionar memòria dinàmicament. Amb malloc() i free() podem reservar i alliberar memòria en temps d'execució, essencial per a seqüències de longitud desconeguda, fitxers FASTA, llistes de pacients, etc.
Objectius
- Declarar punters i utilitzar els operadors
&i* - Comprendre l'aritmètica de punters i la relació amb arrays
- Reservar i alliberar memòria amb
malloc(),calloc(),realloc()ifree() - Crear cadenes i estructures de mida variable
- Gestionar seqüències d'ADN de longitud arbitrària
1. Punters bàsics
Un punter guarda l'adreça d'una variable. Operador & per obtenir l'adreça i * per desreferenciar (accedir al valor).
#include <stdio.h>
int main() {
int valor = 42;
int *punter = &valor; // punter apunta a valor
printf("Valor: %d\n", valor);
printf("Adreça: %p\n", (void*)punter);
printf("Valor via punter: %d\n", *punter);
*punter = 100; // modifiquem valor a través del punter
printf("Nou valor: %d\n", valor); // 100
return 0;
}
& obté l'adreça, * accedeix al valor apuntat
1.1 Aritmètica de punters
Sumar o restar a un punter avança en blocs de la mida del tipus apuntat.
int nums[] = {10, 20, 30};
int *p = nums; // apunta al primer element
printf("%d\n", *p); // 10
p++; // avança 4 bytes (sizeof(int))
printf("%d\n", *p); // 20
2. Memòria dinàmica
Les funcions malloc(), calloc(), realloc() i free() permeten gestionar memòria al heap (memòria dinàmica).
2.1 malloc() i free()
Reserva un bloc de la mida especificada en bytes. Retorna NULL si falla.
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("Quants enters? ");
scanf("%d", &n);
int *array = (int *)malloc(n * sizeof(int));
if (array == NULL) {
printf("Error de memòria\n");
return 1;
}
for (int i = 0; i < n; i++) {
array[i] = i * 10;
printf("%d ", array[i]);
}
printf("\n");
free(array); // alliberar
return 0;
}
malloc retorna void*, cal fer cast al tipus desitjat
2.2 calloc() (inicialitza a zero)
int *array = (int *)calloc(n, sizeof(int)); // tots a 0
if (array == NULL) { /* error */ }
2.3 realloc() (redimensionar)
Canvia la mida d'un bloc de memòria prèviament reservat.
int *array = (int *)malloc(5 * sizeof(int));
// ... omplim ...
int *nou = (int *)realloc(array, 10 * sizeof(int));
if (nou == NULL) {
// error, però array encara és vàlid
free(array);
return 1;
}
array = nou;
// ara podem usar els 10 espais
realloc falla, el bloc original no es perd
3. Cadenes de mida variable
Llegim una seqüència d'ADN sense límit fix mitjançant memòria dinàmica.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* llegir_sequencia() {
char buffer[1000];
printf("Introdueix la seqüència: ");
fgets(buffer, 1000, stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // eliminar salt
char *seq = (char *)malloc((strlen(buffer) + 1) * sizeof(char));
if (seq == NULL) return NULL;
strcpy(seq, buffer);
return seq;
}
int main() {
char *adn = llegir_sequencia();
if (adn) {
printf("Longitud: %zu\n", strlen(adn));
printf("Seqüència: %s\n", adn);
free(adn);
}
return 0;
}
4. Array de punters (vector de cadenes)
Reservem un vector de punters per emmagatzemar múltiples seqüències de longitud variable.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int num_seq;
printf("Nombre de seqüències: ");
scanf("%d", &num_seq);
getchar(); // consumir salt
char **sequencies = (char **)malloc(num_seq * sizeof(char *));
if (sequencies == NULL) return 1;
for (int i = 0; i < num_seq; i++) {
char buffer[1000];
printf("Seq %d: ", i + 1);
fgets(buffer, 1000, stdin);
buffer[strcspn(buffer, "\n")] = '\0';
sequencies[i] = (char *)malloc((strlen(buffer) + 1) * sizeof(char));
strcpy(sequencies[i], buffer);
}
// Mostrar
for (int i = 0; i < num_seq; i++) {
printf("%d: %s (%zu bases)\n", i + 1, sequencies[i], strlen(sequencies[i]));
}
// Alliberar
for (int i = 0; i < num_seq; i++) free(sequencies[i]);
free(sequencies);
return 0;
}
char ** = vector de punters a cadenes
5. Funcions amb punters i memòria dinàmica
Les funcions poden retornar memòria dinàmica o modificar punters a través de paràmetres.
// Retorna la complementària (A↔T, C↔G) en memòria dinàmica
char* complementaria(const char *seq) {
int len = strlen(seq);
char *res = (char *)malloc((len + 1) * sizeof(char));
if (res == NULL) return NULL;
for (int i = 0; i < len; i++) {
switch (seq[i]) {
case 'A': res[i] = 'T'; break;
case 'T': res[i] = 'A'; break;
case 'C': res[i] = 'G'; break;
case 'G': res[i] = 'C'; break;
default: res[i] = seq[i]; // caràcter no vàlid
}
}
res[len] = '\0';
return res;
}
int main() {
char *adn = "AGCT";
char *comp = complementaria(adn);
if (comp) {
printf("Complementària: %s\n", comp);
free(comp);
}
return 0;
}
Exercicis
- Intercanvi: Escriu una funció
swap(int *a, int *b)que intercanviï dos enters mitjançant punters. - Vector dinàmic: Llegeix un nombre enter
n, reserva un vector defloatde midan, omple'l i calcula la mitjana i la desviació estàndard. - Realloc: Crea un programa que llegeixi enters fins que l'usuari introdueixi 0, emmagatzemant-los en un array que creixi amb
realloc(). - Inversa complementària: Fes una funció que retorni la inversa complementària d'una cadena d'ADN (ex: "ATGC" → "GCAT") utilitzant memòria dinàmica.
- Llista de noms: Llegeix noms de pacients fins que l'usuari introdueixi "fi" i emmagatzema'ls en un vector de punters (memòria dinàmica). Després ordena'ls alfabèticament i mostra'ls.
Mini Projecte – Gestor de seqüències FASTA dinàmic
Crea un programa amb un menú que permeti:
- 1. Afegir seqüència: Demana capçalera (ex:
>gen1) i seqüència (pot ser llarga), reservant memòria exacta per a cadascuna. - 2. Llistar seqüències: Mostra capçalera, longitud i %GC de cada seqüència.
- 3. Analitzar una seqüència: Per a una capçalera donada, mostra el nombre de cada base, %GC i la complementària.
- 4. Guardar a fitxer: Desa totes les seqüències en format FASTA en un fitxer anomenat
output.fasta. - 0. Sortir.
Utilitza estructures dinàmiques (malloc, realloc, free) i funcions modulars. Assegura't d'alliberar tota la memòria abans de sortir.