Struct with pointers in C: solved exercise

Struct with pointers in C: solved exercise

If you searched for a solved struct with pointers in C, here is the critical difference between a shallow copy (with = or memcpy) and a deep copy, and why shallow-copying a struct with pointer members is a common source of bugs.

When a struct contains a char * or any pointer, a shallow copy duplicates the pointer but not the pointed-to data: both structs point to the same memory, and freeing one invalidates the other.

Problem statement

Define an Employee struct with fields name (dynamic pointer), age, and salary. Implement:

  1. employee_create(name, age, salary): dynamically allocates the struct and duplicates the string.
  2. employee_copy(src): deep copy — new struct and new string.
  3. employee_free(e): frees the string and then the struct.

Demonstrate that modifying the copy does not affect the original.

C solution

 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   *name;
    int     age;
    double  salary;
} Employee;

Employee *employee_create(const char *name, int age, double salary) {
    Employee *e = malloc(sizeof(Employee));
    if (!e) return NULL;
    e->name = malloc(strlen(name) + 1);
    if (!e->name) { free(e); return NULL; }
    strcpy(e->name, name);
    e->age    = age;
    e->salary = salary;
    return e;
}

/* Deep copy: new struct, new string */
Employee *employee_copy(const Employee *src) {
    return employee_create(src->name, src->age, src->salary);
}

void employee_free(Employee *e) {
    if (!e) return;
    free(e->name);   /* free the string first */
    free(e);
}

void employee_print(const char *label, const Employee *e) {
    printf("%s: name=%-15s  age=%d  salary=%.2f  [name ptr=%p]\n",
           label, e->name, e->age, e->salary, (void *)e->name);
}

int main(void) {
    Employee *orig = employee_create("Alice Johnson", 30, 2500.00);
    if (!orig) { perror("create"); return 1; }

    Employee *copy = employee_copy(orig);
    if (!copy) { perror("copy"); employee_free(orig); return 1; }

    employee_print("Original", orig);
    employee_print("Copy    ", copy);

    /* Modifying the copy must not affect the original */
    free(copy->name);
    copy->name = malloc(strlen("Bob Smith") + 1);
    strcpy(copy->name, "Bob Smith");
    copy->salary = 3100.00;

    printf("\nAfter modifying the copy:\n");
    employee_print("Original", orig);
    employee_print("Copy    ", copy);

    employee_free(orig);
    employee_free(copy);
    return 0;
}

Expected output

1
2
3
4
5
6
Original: name=Alice Johnson    age=30  salary=2500.00  [name ptr=0x...]
Copy    : name=Alice Johnson    age=30  salary=2500.00  [name ptr=0x...]

After modifying the copy:
Original: name=Alice Johnson    age=30  salary=2500.00  [name ptr=0x...]
Copy    : name=Bob Smith        age=30  salary=3100.00  [name ptr=0x...]

(Pointer addresses will differ between original and copy, proving they are independent.)

Common mistakes

  • Copying the struct with copy = *orig or memcpy: duplicates the name pointer, not the string. Both structs point to the same string; freeing one invalidates the other’s pointer.
  • Freeing e before freeing e->name: the name address is lost with the struct, causing a memory leak.
  • Using strcpy without allocating memory for the destination: strcpy assumes the destination already has enough space; without a prior malloc the memory is corrupted.
  • Not checking the return value of malloc for the string: if it fails after the struct was already allocated, the struct must be freed before returning NULL.

Practical use

This pattern is essential in any C module managing data with dynamic strings or buffers: in-memory database records, message queues, caching systems, and any structure that must be copied or serialized to pass to another function or thread.

Guided practice and full book

If you want a complete path with progressive difficulty:

FAQ

When is a shallow copy acceptable for structs with pointers?

Only when the copy’s lifetime does not exceed the original’s and you can guarantee that the original will not be freed while the copy is in use. In practice this guarantee is hard to maintain; deep copy is the safer default rule.

Is there an equivalent of clone or a copy constructor in C?

Not natively. The usual pattern is to manually implement an type_copy(const Type *src) function that performs the deep copy, following the same convention as employee_copy in this exercise.

What happens if the same memory is freed twice (double free)?

It is undefined behavior. In practice it usually corrupts the allocator’s internal heap structures, which can cause segmentation faults, infinite loops, or security vulnerabilities. To detect it, tools like Valgrind or AddressSanitizer (gcc -fsanitize=address) are indispensable.