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:
Allocate memory for the structure
p_dog = malloc ( sizeof ( * p_dog));
Check for allocation failure
if (p_dog == NULL || ! (name) || ! (owner))
Allocate memory for string members
p_dog -> name = malloc (lname + 1 ); // +1 for null terminator
Handle partial allocation failures
if ( ! (p_dog -> name) || ! (p_dog -> owner))
{
free ( p_dog -> owner );
free ( p_dog -> name );
free (p_dog);
return ( NULL );
}
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!
Free the string members first (name, owner)
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
Use meaningful structure names
struct dog; // Good
struct d; // Bad
Document your structures
/**
* struct dog - represents a dog
* @name : name of the dog
* @age: age in years
* @owner: owner's name
*/
Use typedef for cleaner code
typedef struct dog dog_t ;
dog_t my_dog; // Cleaner than: struct dog my_dog;
Always check pointers
if (d && d -> name)
{
printf ( " %s \n " , d -> name );
}
Free memory in reverse order
free (d -> name ); // Free members first
free (d -> owner );
free (d); // Free structure last
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