C/C++

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() i free()
  • 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
El nom d'un array és un punter al seu primer element

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
Si 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;
}
La funció retorna memòria dinàmica que cal alliberar

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;
}
La funció retorna memòria que el caller ha d'alliberar

Exercicis

  1. Intercanvi: Escriu una funció swap(int *a, int *b) que intercanviï dos enters mitjançant punters.
  2. Vector dinàmic: Llegeix un nombre enter n, reserva un vector de float de mida n, omple'l i calcula la mitjana i la desviació estàndard.
  3. Realloc: Crea un programa que llegeixi enters fins que l'usuari introdueixi 0, emmagatzemant-los en un array que creixi amb realloc().
  4. 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.
  5. 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.