Skip to main content

Introduction to Structures

Structures (structs) allow you to group related data of different types under a single name. They’re essential for organizing complex data and creating custom data types.
Think of a struct as a container that holds related information together. For example, a dog has a name, age, and owner - perfect for grouping in a structure.

Defining a Structure

Here’s the actual dog structure from the project:
#ifndef DOG_
#define DOG_

/**
 * struct dog - dog
 * @name: name of dog
 * @age: age of dog
 * @owner: owner of dog
 */
struct dog
{
    char *name;
    float age;
    char *owner;
};

#endif
Key components:
  • struct dog - The structure tag (name)
  • Members: name, age, owner
  • Different data types in one structure
  • Enclosed in curly braces with a semicolon at the end

Declaring Structure Variables

Method 1: Using struct keyword

struct dog my_dog;
struct dog another_dog;

Method 2: At definition time

struct dog
{
    char *name;
    float age;
    char *owner;
} my_dog, another_dog;

Accessing Structure Members

Use the dot operator (.) to access members:
struct dog my_dog;

my_dog.name = "Fluffy";
my_dog.age = 3.5;
my_dog.owner = "John";

printf("Name: %s\n", my_dog.name);
printf("Age: %.1f\n", my_dog.age);
printf("Owner: %s\n", my_dog.owner);

Initializing Structures

Using an Initialization Function

From the project’s 1-init_dog.c:
#include "dog.h"

/**
 * init_dog - initializes a variable of type struct dog.
 * @d: struct dog
 * @name: name of the dog
 * @age: age of the dog
 * @owner: owner of the dog
 * Return: NUll.
 */
void init_dog(struct dog *d, char *name, float age, char *owner)
{
    if (d)
    {
        d->name = name;
        d->age = age;
        d->owner = owner;
    }
}
Important notes:
  • Takes a pointer to a struct: struct dog *d
  • Uses arrow operator (->) to access members through a pointer
  • Checks if pointer is valid before accessing
Usage:
struct dog my_dog;
init_dog(&my_dog, "Fluffy", 3.5, "John");

Designated Initializers

struct dog my_dog = {
    .name = "Fluffy",
    .age = 3.5,
    .owner = "John"
};

Pointers to Structures

When working with pointers to structures, use the arrow operator (->):
struct dog my_dog;
struct dog *ptr = &my_dog;

ptr->name = "Fluffy";   // Same as (*ptr).name = "Fluffy"
ptr->age = 3.5;         // Same as (*ptr).age = 3.5
ptr->owner = "John";    // Same as (*ptr).owner = "John"
Operator equivalence:
  • ptr->name is equivalent to (*ptr).name
  • The arrow operator (->) is just syntactic sugar for dereferencing and accessing

Printing Structure Data

From 2-print_dog.c:
#include "dog.h"
#include <stdio.h>

/**
 * print_dog - prints a struct dog
 * @d: struct dog
 * Return: NULL
 */
void print_dog(struct dog *d)
{
    if (d)
    {
        if (!(d->name))
            printf("Name: (nil)\n");
        else
            printf("Name: %s\n", d->name);

        printf("Age: %f\n", d->age);

        if (!(d->owner))
            printf("Owner: (nil)\n");
        else
            printf("Owner: %s\n", d->owner);
    }
}
Key practices demonstrated:
  • Check if the struct pointer is NULL before accessing
  • Check if string pointers are NULL before printing
  • Print “(nil)” for NULL pointers
  • Handle all edge cases safely
Always validate pointers before dereferencing! Both the struct pointer and any pointer members should be checked for NULL.

Typedef - Creating Type Aliases

The typedef keyword creates an alias for a type, making code cleaner and more readable.

Basic Typedef Syntax

typedef existing_type new_name;

Typedef with Structures

From dog.h:
/**
 * dog_t - Typedef for struct dog
 */
typedef struct dog dog_t;
Benefits: Without typedef:
struct dog my_dog;
struct dog *ptr;
struct dog create_dog(void);
With typedef:
dog_t my_dog;
dog_t *ptr;
dog_t create_dog(void);
Much cleaner and easier to read!

Typedef in Definition

You can combine struct definition with typedef:
typedef struct dog
{
    char *name;
    float age;
    char *owner;
} dog_t;

Dynamic Memory Allocation with Structures

From 4-new_dog.c, here’s how to create a dog structure dynamically:
#include "dog.h"
#include <stdlib.h>

/**
 * new_dog - creates a new dog.
 * @name: name of the dog.
 * @age: age of the dog.
 * @owner: owner of the dog.
 *
 * Return: struct dog.
 * if fails, returns NULL.
 */
dog_t *new_dog(char *name, float age, char *owner)
{
    dog_t *p_dog;
    int i, lname, lowner;

    p_dog = malloc(sizeof(*p_dog));
    if (p_dog == NULL || !(name) || !(owner))
    {
        free(p_dog);
        return (NULL);
    }

    for (lname = 0; name[lname]; lname++)
        ;

    for (lowner = 0; owner[lowner]; lowner++)
        ;

    p_dog->name = malloc(lname + 1);
    p_dog->owner = malloc(lowner + 1);

    if (!(p_dog->name) || !(p_dog->owner))
    {
        free(p_dog->owner);
        free(p_dog->name);
        free(p_dog);
        return (NULL);
    }

    for (i = 0; i < lname; i++)
        p_dog->name[i] = name[i];
    p_dog->name[i] = '\0';

    p_dog->age = age;

    for (i = 0; i < lowner; i++)
        p_dog->owner[i] = owner[i];
    p_dog->owner[i] = '\0';

    return (p_dog);
}
Key techniques:
  1. Allocate memory for the structure
    p_dog = malloc(sizeof(*p_dog));
    
  2. Check for allocation failure
    if (p_dog == NULL || !(name) || !(owner))
    
  3. Allocate memory for string members
    p_dog->name = malloc(lname + 1);  // +1 for null terminator
    
  4. Handle partial allocation failures
    if (!(p_dog->name) || !(p_dog->owner))
    {
        free(p_dog->owner);
        free(p_dog->name);
        free(p_dog);
        return (NULL);
    }
    
  5. Copy strings manually
    for (i = 0; i < lname; i++)
        p_dog->name[i] = name[i];
    p_dog->name[i] = '\0';
    

Freeing Structure Memory

From 5-free_dog.c:
#include "dog.h"
#include <stdlib.h>

/**
 * free_dog - frees dogs.
 * @d: struct dog.
 *
 * Return: no return.
 */
void free_dog(dog_t *d)
{
    if (d)
    {
        free(d->name);
        free(d->owner);
        free(d);
    }
}
Memory deallocation order is critical!
  1. Free the string members first (name, owner)
  2. Then free the structure itself
If you free the structure first, you lose access to the members and create a memory leak!

Nested Structures

Structures can contain other structures:
struct address
{
    char *street;
    char *city;
    int zip;
};

struct person
{
    char *name;
    int age;
    struct address addr;  // Nested structure
};
Accessing nested members:
struct person p;
p.addr.street = "123 Main St";
p.addr.city = "New York";
p.addr.zip = 10001;

Arrays of Structures

dog_t kennel[10];  // Array of 10 dogs

// Initialize first dog
kennel[0].name = "Fluffy";
kennel[0].age = 3.5;
kennel[0].owner = "John";

// Initialize second dog
kennel[1].name = "Buddy";
kennel[1].age = 5.0;
kennel[1].owner = "Sarah";

// Loop through all dogs
for (int i = 0; i < 10; i++)
{
    if (kennel[i].name != NULL)
        printf("Dog %d: %s\n", i, kennel[i].name);
}

Structure Padding and Alignment

The compiler may add padding between structure members for alignment. This affects the structure size.
struct example
{
    char a;    // 1 byte
    // 3 bytes padding
    int b;     // 4 bytes
    char c;    // 1 byte
    // 3 bytes padding
};
// Total: 12 bytes, not 6!
Use sizeof(struct example) to get the actual size.

Best Practices

  1. Use meaningful structure names
    struct dog;          // Good
    struct d;            // Bad
    
  2. Document your structures
    /**
     * struct dog - represents a dog
     * @name: name of the dog
     * @age: age in years
     * @owner: owner's name
     */
    
  3. Use typedef for cleaner code
    typedef struct dog dog_t;
    dog_t my_dog;  // Cleaner than: struct dog my_dog;
    
  4. Always check pointers
    if (d && d->name)
    {
        printf("%s\n", d->name);
    }
    
  5. Free memory in reverse order
    free(d->name);   // Free members first
    free(d->owner);
    free(d);         // Free structure last
    
  6. Use designated initializers for clarity
    dog_t my_dog = {
        .name = "Fluffy",
        .age = 3.5,
        .owner = "John"
    };
    

Common Use Cases

Data Organization

Group related data (person info, coordinates, dates)

Function Returns

Return multiple values from a function

Linked Lists

Create nodes with data and pointer to next

System Programming

Represent system resources and configurations

Key Takeaways

  • Structures group related data of different types
  • Use . for direct access, -> for pointer access
  • typedef creates aliases for cleaner code
  • Always validate pointers before dereferencing
  • Free members before freeing the structure
  • Structure size may include padding for alignment

Function Pointers

Learn to store functions in structures

Bit Manipulation

Work with bit fields in structures

Build docs developers (and LLMs) love