C Programming: Low-Level Mastery
HomeInsightsCoursesC ProgrammingDefining & Using Structures
Structures & Unions

Structures

Master C structures - user-defined types grouping related data. Learn declaration, initialization, member access, nested structures, structure arrays, and how structures organize complex data efficiently.

What Are Structures?

Structures (structs) group related variables under one name. Unlike arrays (same type), structures hold different types. They're the foundation of data modeling in C, enabling you to represent real-world entities like students, employees, or complex numbers as single units.

C
#include <stdio.h>
#include <string.h>

/* Define a structure */
struct Student {
    char name[50];
    int id;
    float gpa;
};

int main(void) {
    /* Declare structure variable */
    struct Student s1;
    
    /* Access members with dot operator */
    strcpy(s1.name, "Alice");
    s1.id = 12345;
    s1.gpa = 3.8;
    
    /* Print structure data */
    printf("Name: %s\n", s1.name);
    printf("ID: %d\n", s1.id);
    printf("GPA: %.2f\n", s1.gpa);
    
    return 0;
}

/* Memory layout (conceptual):
   ┌──────────────────────────┐
   │ name[50]  (50 bytes)    │
   ├──────────────────────────┤
   │ id        (4 bytes)      │
   ├──────────────────────────┤
   │ gpa       (4 bytes)      │
   └──────────────────────────┘
   Total: ~58 bytes (+ padding)
*/

Structure Declaration and Definition

Structures can be declared in several ways. Using typedef creates an alias, simplifying declarations. Choose the style that fits your codebase conventions and readability needs.

C
/* Method 1: Basic structure */
struct Point {
    int x;
    int y;
};

struct Point p1;  /* Must use 'struct' keyword */

/* Method 2: typedef for convenience */
typedef struct {
    int x;
    int y;
} Point;

Point p2;  /* No 'struct' needed */

/* Method 3: typedef with tag name */
typedef struct Point_t {
    int x;
    int y;
} Point;

struct Point_t p3;  /* Can use tag */
Point p4;           /* Or typedef name */

/* Method 4: Declare variables with definition */
struct Rectangle {
    int width;
    int height;
} rect1, rect2;  /* Two variables */

/* Empty structure (valid but useless) */
struct Empty {
    /* No members */
};  /* sizeof(struct Empty) may be 0 or 1 */

/* Structure with various types */
typedef struct {
    char name[100];
    int age;
    float salary;
    double balance;
    char grade;
    int scores[5];
} Employee;

/* Forward declaration (for self-referential structures) */
struct Node;  /* Declare existence */

struct Node {
    int data;
    struct Node *next;  /* Pointer to same type */
};

/* Multiple structures */
struct Date {
    int day;
    int month;
    int year;
};

struct Person {
    char name[50];
    struct Date birthday;  /* Nested structure */
    int age;
};

Structure Initialization

Structures can be initialized in multiple ways. Designated initializers (C99+) provide clarity and allow partial initialization. Always initialize structures to avoid garbage values in uninitialized members.

C
typedef struct {
    int x;
    int y;
} Point;

typedef struct {
    char name[50];
    int age;
    float salary;
} Employee;

/* Method 1: Zero initialization */
Point p1 = {0};  /* All members set to 0 */
Employee e1 = {0};  /* All members zeroed */

/* Method 2: Ordered initialization */
Point p2 = {10, 20};  /* x=10, y=20 */
Employee e2 = {"John", 30, 50000.0};

/* Method 3: Partial initialization */
Point p3 = {10};  /* x=10, y=0 (rest zeroed) */

/* Method 4: Designated initializers (C99+) */
Point p4 = {.x = 5, .y = 15};
Point p5 = {.y = 25, .x = 10};  /* Order doesn't matter */
Point p6 = {.x = 7};  /* y is 0 */

Employee e3 = {
    .name = "Alice",
    .age = 28,
    .salary = 65000.0
};

/* Compound literals (C99+) */
void print_point(Point p) {
    printf("(%d, %d)\n", p.x, p.y);
}

print_point((Point){.x = 3, .y = 4});

/* Array of structures initialization */
Point points[] = {
    {1, 2},
    {3, 4},
    {5, 6}
};

Employee employees[] = {
    {"Alice", 28, 65000},
    {"Bob", 32, 70000},
    {.name = "Carol", .age = 25}  /* salary is 0 */
};

/* Dynamic initialization */
Point p7;
p7.x = 100;
p7.y = 200;

/* Copy initialization */
Point p8 = p7;  /* Copies all members */

/* Nested structure initialization */
typedef struct {
    int day;
    int month;
    int year;
} Date;

typedef struct {
    char name[50];
    Date birthday;
    int age;
} Person;

Person person1 = {
    .name = "John",
    .birthday = {25, 12, 1990},
    .age = 33
};

/* Or with designated initializers */
Person person2 = {
    "Jane",
    {.year = 1995, .month = 6, .day = 15},
    28
};

Accessing and Modifying Members

The dot operator (.) accesses structure members. Arrow operator (-&gt;) accesses members through pointers. Structures can be assigned, passed to functions, and returned from functions.

C
typedef struct {
    int x;
    int y;
} Point;

/* Dot operator for direct access */
Point p1 = {10, 20};
printf("x: %d, y: %d\n", p1.x, p1.y);

p1.x = 30;
p1.y = 40;

/* Arrow operator for pointer access */
Point *ptr = &p1;
printf("x: %d, y: %d\n", ptr-&gt;x, ptr-&gt;y);

/* Equivalent to: */
printf("x: %d, y: %d\n", (*ptr).x, (*ptr).y);

/* Structure assignment */
Point p2 = p1;  /* Copies all members */
p2.x = 100;     /* p1.x is unchanged */

/* Nested structure access */
typedef struct {
    int day;
    int month;
    int year;
} Date;

typedef struct {
    char name[50];
    Date birthday;
} Person;

Person person = {"Alice", {15, 3, 1995}};

/* Access nested members */
printf("Day: %d\n", person.birthday.day);
person.birthday.year = 1996;

/* With pointer */
Person *pptr = &person;
printf("Month: %d\n", pptr-&gt;birthday.month);
pptr-&gt;birthday.day = 20;

/* Array member access */
typedef struct {
    char name[50];
    int scores[5];
} Student;

Student student = {"Bob", {85, 90, 78, 92, 88}};

printf("First score: %d\n", student.scores[0]);
student.scores[2] = 95;

for (int i = 0; i < 5; i++) {
    printf("Score %d: %d\n", i, student.scores[i]);
}

/* Pointer member */
typedef struct {
    char *name;  /* Pointer to string */
    int age;
} PersonDynamic;

PersonDynamic pd;
pd.name = "Charlie";  /* Points to string literal */
pd.age = 30;

/* Or allocate memory */
pd.name = malloc(50);
if (pd.name != NULL) {
    strcpy(pd.name, "Dynamic Name");
    free(pd.name);
}

Structures and Functions

Structures can be passed to functions by value (copy) or by pointer (reference). Passing by pointer is more efficient for large structures and allows modification. Use const pointers for read-only access.

C
typedef struct {
    int x;
    int y;
} Point;

/* Pass by value (copy) */
void print_point_value(Point p) {
    printf("(%d, %d)\n", p.x, p.y);
    p.x = 999;  /* Doesn't affect original */
}

/* Pass by pointer (reference) */
void print_point_ptr(const Point *p) {
    printf("(%d, %d)\n", p-&gt;x, p-&gt;y);
    // p-&gt;x = 999;  /* Compiler error: const */
}

/* Modify through pointer */
void move_point(Point *p, int dx, int dy) {
    p-&gt;x += dx;
    p-&gt;y += dy;
}

/* Return structure by value */
Point create_point(int x, int y) {
    Point p = {x, y};
    return p;  /* Returns copy */
}

/* Return structure through pointer parameter */
void create_point_ptr(Point *p, int x, int y) {
    p-&gt;x = x;
    p-&gt;y = y;
}

/* Calculate with structures */
Point add_points(Point p1, Point p2) {
    Point result = {p1.x + p2.x, p1.y + p2.y};
    return result;
}

/* Or more efficient with pointers */
void add_points_ptr(const Point *p1, const Point *p2, Point *result) {
    result-&gt;x = p1-&gt;x + p2-&gt;x;
    result-&gt;y = p1-&gt;y + p2-&gt;y;
}

/* Usage */
void structure_function_examples(void) {
    Point p1 = {10, 20};
    
    /* Pass by value */
    print_point_value(p1);
    printf("p1: (%d, %d)\n", p1.x, p1.y);  /* Unchanged */
    
    /* Pass by pointer */
    print_point_ptr(&p1);
    
    /* Modify */
    move_point(&p1, 5, 10);
    printf("After move: (%d, %d)\n", p1.x, p1.y);  /* (15, 30) */
    
    /* Create */
    Point p2 = create_point(100, 200);
    
    /* Add */
    Point p3 = add_points(p1, p2);
    printf("Sum: (%d, %d)\n", p3.x, p3.y);
}

/* Comparison function */
int points_equal(const Point *p1, const Point *p2) {
    return p1-&gt;x == p2-&gt;x && p1-&gt;y == p2-&gt;y;
}

/* Complex structure operations */
typedef struct {
    char name[50];
    int age;
    float salary;
} Employee;

void give_raise(Employee *emp, float percentage) {
    emp-&gt;salary *= (1.0 + percentage / 100.0);
}

void print_employee(const Employee *emp) {
    printf("Name: %s, Age: %d, Salary: $%.2f\n",
           emp-&gt;name, emp-&gt;age, emp-&gt;salary);
}

/* Array of structures as parameter */
void print_employees(const Employee *employees, int count) {
    for (int i = 0; i < count; i++) {
        print_employee(&employees[i]);
    }
}

Structure Arrays and Memory

Arrays of structures store multiple structure instances. Understand memory layout, padding, and alignment for efficient structure usage. Use sizeof to get actual structure size including padding.

C
typedef struct {
    int id;
    char name[50];
    float gpa;
} Student;

/* Array of structures */
Student students[100];

/* Initialize array */
Student class[] = {
    {1, "Alice", 3.8},
    {2, "Bob", 3.5},
    {3, "Carol", 3.9}
};

int num_students = sizeof(class) / sizeof(class[0]);

/* Access array elements */
for (int i = 0; i < num_students; i++) {
    printf("ID: %d, Name: %s, GPA: %.2f\n",
           class[i].id, class[i].name, class[i].gpa);
}

/* Modify array elements */
class[0].gpa = 4.0;
strcpy(class[1].name, "Robert");

/* Pointer to array element */
Student *ptr = &class[0];
printf("First student: %s\n", ptr-&gt;name);

ptr++;  /* Move to next student */
printf("Second student: %s\n", ptr-&gt;name);

/* Memory layout and padding */
struct Example1 {
    char c;    /* 1 byte */
    int i;     /* 4 bytes */
};
/* Likely size: 8 bytes (3 bytes padding after c) */

struct Example2 {
    char c1;   /* 1 byte */
    char c2;   /* 1 byte */
    int i;     /* 4 bytes */
};
/* Likely size: 8 bytes (2 bytes padding after c2) */

struct Example3 {
    int i;     /* 4 bytes */
    char c1;   /* 1 byte */
    char c2;   /* 1 byte */
};
/* Likely size: 8 bytes (2 bytes padding at end) */

/* Print sizes */
printf("Example1: %zu bytes\n", sizeof(struct Example1));
printf("Example2: %zu bytes\n", sizeof(struct Example2));
printf("Example3: %zu bytes\n", sizeof(struct Example3));

/* Dynamic array of structures */
int capacity = 10;
Student *dynamic_students = malloc(capacity * sizeof(Student));

if (dynamic_students != NULL) {
    /* Use like regular array */
    dynamic_students[0].id = 1;
    strcpy(dynamic_students[0].name, "Diana");
    dynamic_students[0].gpa = 3.7;
    
    free(dynamic_students);
}

/* Sorting structures */
int compare_by_gpa(const void *a, const void *b) {
    const Student *s1 = (const Student*)a;
    const Student *s2 = (const Student*)b;
    
    if (s1-&gt;gpa < s2-&gt;gpa) return 1;
    if (s1-&gt;gpa &gt; s2-&gt;gpa) return -1;
    return 0;
}

qsort(class, num_students, sizeof(Student), compare_by_gpa);

/* Find in array */
Student* find_student_by_id(Student *students, int count, int id) {
    for (int i = 0; i < count; i++) {
        if (students[i].id == id) {
            return &students[i];
        }
    }
    return NULL;
}

Nested and Self-Referential Structures

Structures can contain other structures (nesting) or pointers to their own type (self-referential). Self-referential structures form the basis of linked lists, trees, and other dynamic data structures.

C
/* Nested structures */
typedef struct {
    int day;
    int month;
    int year;
} Date;

typedef struct {
    int hours;
    int minutes;
    int seconds;
} Time;

typedef struct {
    Date date;
    Time time;
} DateTime;

DateTime dt = {
    .date = {25, 12, 2023},
    .time = {14, 30, 0}
};

printf("Date: %d/%d/%d\n", dt.date.day, dt.date.month, dt.date.year);
printf("Time: %02d:%02d:%02d\n", dt.time.hours, dt.time.minutes, dt.time.seconds);

/* Self-referential structure (linked list node) */
typedef struct Node {
    int data;
    struct Node *next;  /* Pointer to same type */
} Node;

/* Create nodes */
Node node1 = {10, NULL};
Node node2 = {20, NULL};
Node node3 = {30, NULL};

/* Link nodes */
node1.next = &node2;
node2.next = &node3;

/* Traverse */
Node *current = &node1;
while (current != NULL) {
    printf("%d -&gt; ", current-&gt;data);
    current = current-&gt;next;
}
printf("NULL\n");

/* Binary tree node */
typedef struct TreeNode {
    int value;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

TreeNode root = {10, NULL, NULL};
TreeNode left = {5, NULL, NULL};
TreeNode right = {15, NULL, NULL};

root.left = &left;
root.right = &right;

/* Complex nesting */
typedef struct {
    char street[100];
    char city[50];
    char state[30];
    int zip;
} Address;

typedef struct {
    char name[50];
    Address home;
    Address work;
} Person;

Person person = {
    "John Doe",
    {"123 Main St", "Springfield", "IL", 62701},
    {"456 Work Ave", "Chicago", "IL", 60601}
};

printf("Home: %s, %s\n", person.home.street, person.home.city);
printf("Work: %s, %s\n", person.work.street, person.work.city);

/* Cannot directly contain own type */
// struct Invalid {
//     int data;
//     struct Invalid next;  /* ERROR: Infinite size! */
// };

/* But can contain pointer to own type */
struct Valid {
    int data;
    struct Valid *next;  /* OK: Pointer has fixed size */
};

Summary & What's Next

Key Takeaways:

  • ✅ Structures group related data of different types
  • ✅ Use dot (.) for direct access, arrow (-&gt;) for pointers
  • ✅ typedef simplifies structure declarations
  • ✅ Initialize structures to avoid garbage values
  • ✅ Pass large structures by pointer for efficiency
  • ✅ sizeof includes padding for alignment
  • ✅ Self-referential structures use pointers
  • ✅ Structures enable modeling complex data relationships

What's Next?

Let's learn about unions and their memory-efficient alternative to structures!