Struct con punteros en C: ejercicio resuelto

Struct con punteros en C: ejercicio resuelto

Si buscas struct con punteros en C ejercicio resuelto, aquí tienes la diferencia crítica entre copia superficial (shallow copy, con = o memcpy) y copia profunda (deep copy), y por qué la copia superficial de una struct con miembros puntero es una fuente habitual de errores.

Cuando una struct contiene un char * o cualquier puntero, la copia superficial duplica el puntero pero no los datos apuntados: ambas structs apuntan a la misma memoria y liberar una invalida a la otra.

Enunciado

Define una struct Empleado con campos nombre (puntero dinámico), edad y salario. Implementa:

  1. empleado_crear(nombre, edad, salario): asigna dinámicamente la struct y duplica la cadena.
  2. empleado_copiar(src): copia profunda — nueva struct y nueva cadena.
  3. empleado_liberar(e): libera la cadena y luego la struct.

Demuestra que modificar la copia no afecta al original.

Solución en C

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char   *nombre;
    int     edad;
    double  salario;
} Empleado;

Empleado *empleado_crear(const char *nombre, int edad, double salario) {
    Empleado *e = malloc(sizeof(Empleado));
    if (!e) return NULL;
    e->nombre = malloc(strlen(nombre) + 1);
    if (!e->nombre) { free(e); return NULL; }
    strcpy(e->nombre, nombre);
    e->edad    = edad;
    e->salario = salario;
    return e;
}

/* Copia profunda: nueva struct, nueva cadena */
Empleado *empleado_copiar(const Empleado *src) {
    return empleado_crear(src->nombre, src->edad, src->salario);
}

void empleado_liberar(Empleado *e) {
    if (!e) return;
    free(e->nombre);   /* liberar la cadena primero */
    free(e);
}

void empleado_imprimir(const char *etiqueta, const Empleado *e) {
    printf("%s: nombre=%s  edad=%d  salario=%.2f  [ptr nombre=%p]\n",
           etiqueta, e->nombre, e->edad, e->salario, (void *)e->nombre);
}

int main(void) {
    Empleado *orig = empleado_crear("Ana García", 30, 2500.00);
    if (!orig) { perror("crear"); return 1; }

    Empleado *copia = empleado_copiar(orig);
    if (!copia) { perror("copiar"); empleado_liberar(orig); return 1; }

    empleado_imprimir("Original", orig);
    empleado_imprimir("Copia   ", copia);

    /* Modificar la copia no debe afectar al original */
    free(copia->nombre);
    copia->nombre = malloc(strlen("Carlos López") + 1);
    strcpy(copia->nombre, "Carlos López");
    copia->salario = 3100.00;

    printf("\nTras modificar la copia:\n");
    empleado_imprimir("Original", orig);
    empleado_imprimir("Copia   ", copia);

    empleado_liberar(orig);
    empleado_liberar(copia);
    return 0;
}

Resultado esperado

1
2
3
4
5
6
Original: nombre=Ana García  edad=30  salario=2500.00  [ptr nombre=0x...]
Copia   : nombre=Ana García  edad=30  salario=2500.00  [ptr nombre=0x...]

Tras modificar la copia:
Original: nombre=Ana García  edad=30  salario=2500.00  [ptr nombre=0x...]
Copia   : nombre=Carlos López  edad=30  salario=3100.00  [ptr nombre=0x...]

(Las direcciones de puntero serán distintas entre original y copia, demostrando que son copias independientes.)

Errores frecuentes

  • Copiar la struct con copia = *orig o memcpy: duplica el puntero nombre, no la cadena. Ambas structs apuntan a la misma cadena; liberar una invalida el puntero de la otra.
  • Liberar e antes de liberar e->nombre: la dirección de nombre se pierde con la struct, causando una fuga de memoria.
  • Usar strcpy sin asignar memoria para el destino: strcpy asume que el destino ya tiene espacio suficiente; sin malloc previo se corrompe la memoria.
  • No comprobar el resultado de malloc para la cadena: si falla tras haber asignado la struct, hay que liberar la struct antes de devolver NULL.

Aplicación práctica

Este patrón es esencial en cualquier módulo de C que gestione datos con cadenas o buffers dinámicos: registros de bases de datos en memoria, colas de mensajes, sistemas de caché y cualquier estructura que deba copiarse o serializarse para enviar a otra función o hilo.

Siguiente ejercicio recomendado

Práctica guiada y libro completo

Si quieres una ruta completa con progresión real de dificultad:

FAQ

¿Cuándo es aceptable usar la copia superficial con structs que tienen punteros?

Solo cuando la vida de la copia no supera la del original y se sabe con certeza que el original no se libera mientras la copia esté en uso. En la práctica, es una garantía difícil de mantener; la copia profunda es más segura como regla general.

¿Existe un equivalente de clone o copy constructor en C?

No de forma nativa. El patrón habitual es implementar manualmente una función tipo_copiar(const Tipo *src) que realiza la copia profunda, siguiendo la misma convención que empleado_copiar en este ejercicio.

¿Qué ocurre si se libera la misma memoria dos veces (double free)?

Es comportamiento indefinido. En la práctica suele corromper las estructuras internas del allocator (heap), lo que puede provocar fallos de segmentación, bucles infinitos o vulnerabilidades de seguridad. Para detectarlo, herramientas como Valgrind o AddressSanitizer (gcc -fsanitize=address) son indispensables.