C Programming Structures
Data Structures

C Structures - Complete Guide

Master C structures including structures vs unions, structure arrays, pointers to structures, nested structures, bit fields, typedef, and programming best practices.

Structures

User-defined types

vs Unions

Key differences

Arrays & Pointers

Advanced usage

Bit Fields

Memory optimization

Introduction to C Structures

Structures (struct) in C allow you to combine different data types into a single user-defined data type. They enable you to create complex data types that group related variables together, making code more organized and manageable.

Why Structures Matter
  • Data Grouping: Combine related data items
  • Code Organization: Better code structure and readability
  • Real-world Modeling: Represent real-world entities (Student, Employee, etc.)
  • Function Arguments: Pass multiple related values as single parameter
  • Data Structures: Foundation for linked lists, trees, graphs
  • Memory Efficiency: Better memory organization than individual variables
Structure Concepts
  • Structure: Collection of different data types
  • Union: Shares memory between members
  • Array of Structures: Multiple structure instances
  • Pointer to Structure: Dynamic structure access
  • typedef: Create type aliases
  • Nested Structures: Structure within structure

Core Concepts of Structures

Structures create user-defined data types that group related variables of different types. Each variable in a structure is called a member. Structures allocate memory for all members separately, while unions share memory among members. Understanding this fundamental difference is crucial for effective C programming.

Structures vs Unions Comparison

Here is a comprehensive comparison of structures and unions in C:

Feature Structure (struct) Union (union)
Keyword struct union
Memory Allocation Memory for all members (sum of sizes) Memory for largest member only
Memory Usage More memory (all members stored) Less memory (shared space)
Access to Members All members accessible simultaneously Only one member valid at a time
Initialization All members can be initialized Only first member can be initialized
Use Case Group related data of different types Store different types in same memory
Size Sum of all member sizes (+ padding) Size of largest member (+ padding)
Example struct Student {int id; char name[50];}; union Data {int i; float f; char str[20];};

Memory Layout Visualization:

Structure Memory Layout
0x1000
int id
4 bytes
0x1004
char name[20]
20 bytes
0x1018
float gpa
4 bytes

Total: 28 bytes (all members have separate memory)

Union Memory Layout
0x1000
int i
4 bytes
0x1000
float f
4 bytes
0x1000
char str[20]
20 bytes

Total: 20 bytes (all members share same memory)

Choosing Between Structure and Union: Use structures when you need to store and access all members simultaneously (like a student record). Use unions when you need to store different types of data in the same memory location at different times (like a variant type or when memory is constrained). Unions are also useful for type punning and hardware register access.

Basic Structures - Declaration and Usage

Structures allow you to define a new data type that groups variables of different types. Each variable in the structure is called a member or field.

Syntax:
// Structure declaration struct structure_name { data_type member1; data_type member2; // ... more members }; // Structure variable declaration struct structure_name variable_name; // Accessing members variable_name.member1 = value;

Key Concepts:

  • Member Access: Use dot (.) operator for structure variables
  • Memory Allocation: Contiguous memory for all members
  • Padding: Compiler may add padding bytes for alignment
  • Initialization: Can initialize during declaration
  • Assignment: Structures can be assigned to each other (member-wise copy)
  • Size: Use sizeof() to get total size

Basic Structure Examples:

Each program below is small and complete. Compile and run them one at a time.

Example 1: Declare a struct and use the dot (.) operator
#include <stdio.h>
#include <string.h>

struct Student {
    int id;
    char name[50];
    float marks;
    char grade;
};

int main(void) {
    struct Student s;
    s.id = 101;
    strcpy(s.name, "John Doe");
    s.marks = 85.5f;
    s.grade = 'A';
    printf("ID=%d, Name=%s, Marks=%.1f, Grade=%c\n",
           s.id, s.name, s.marks, s.grade);

    struct Student t = {102, "Jane Smith", 92.0f, 'A'};
    printf("ID=%d, Name=%s\n", t.id, t.name);
    return 0;
}
Output:
ID=101, Name=John Doe, Marks=85.5, Grade=A
ID=102, Name=Jane Smith
Example 2: Designated initializers (C99) and sizeof
#include <stdio.h>

struct Student {
    int id;
    char name[50];
    float marks;
    char grade;
};

int main(void) {
    struct Student s = {
        .id = 103,
        .name = "Bob Johnson",
        .marks = 78.5f,
        .grade = 'B'
    };
    printf("%s — marks %.1f\n", s.name, s.marks);
    printf("sizeof(struct Student) = %zu bytes (may include padding)\n",
           sizeof(struct Student));
    return 0;
}
Output:
Bob Johnson — marks 78.5
sizeof(struct Student) = 60 bytes (may include padding)
Example 3: Structure assignment copies all members
#include <stdio.h>
#include <string.h>

struct Point {
    int x;
    int y;
};

int main(void) {
    struct Point a = {10, 20};
    struct Point b = a;
    b.x = 99;
    printf("a: (%d,%d)  b: (%d,%d)\n", a.x, a.y, b.x, b.y);
    return 0;
}
Output:
a: (10,20) b: (99,20)
Example 4: Pass struct by value (copy) vs changing the original
#include <stdio.h>

struct Counter {
    int n;
};

void try_change(struct Counter c) {
    c.n = 100;
    printf("inside function: n = %d\n", c.n);
}

int main(void) {
    struct Counter c = {1};
    try_change(c);
    printf("after function: n = %d\n", c.n);
    return 0;
}
Output:
inside function: n = 100
after function: n = 1
Example 5: Return a struct from a function
#include <stdio.h>

struct Rect {
    int width;
    int height;
};

struct Rect make_rect(int w, int h) {
    struct Rect r;
    r.width = w;
    r.height = h;
    return r;
}

int main(void) {
    struct Rect r = make_rect(4, 5);
    printf("area = %d\n", r.width * r.height);
    return 0;
}
Output:
area = 20
Example 6: Compare structs member by member (no == for whole struct)
#include <stdio.h>
#include <string.h>

struct Student {
    int id;
    char name[32];
};

int main(void) {
    struct Student a = {1, "Amy"};
    struct Student b = {1, "Amy"};
    int same = (a.id == b.id) && (strcmp(a.name, b.name) == 0);
    printf("same? %s\n", same ? "yes" : "no");
    return 0;
}
Output:
same? yes
Example 7: Anonymous struct (C11)
#include <stdio.h>

int main(void) {
    struct {
        int x;
        int y;
    } p = {3, 4}, q = {10, 20};

    printf("p=(%d,%d) q=(%d,%d)\n", p.x, p.y, q.x, q.y);
    return 0;
}
Output:
p=(3,4) q=(10,20)
Structure Padding and Alignment: Compilers often add padding bytes between structure members to ensure proper memory alignment for performance. The sizeof() a structure may be larger than the sum of its members. Use #pragma pack(1) to disable padding (but may cause performance issues on some architectures).

Unions - Complete Guide

Unions are similar to structures but all members share the same memory location. Only one member can contain a value at any given time. Unions are useful for saving memory when you need to store different types of data in the same location.

Syntax:
// Union declaration union union_name { data_type member1; data_type member2; // ... more members }; // Union variable declaration union union_name variable_name; // Accessing members variable_name.member1 = value;

Key Characteristics:

  • Shared Memory: All members share the same memory space
  • Size: Size of union = size of its largest member
  • One Active Member: Only one member contains valid data at a time
  • Memory Efficiency: Uses less memory than equivalent structure
  • Type Punning: Can be used to interpret same memory as different types
  • Initialization: Can only initialize first member during declaration

Union Examples:

Each example is a short, complete program. Only the member you last wrote to is valid until you write another.

Example 1: One storage location, different member names
#include <stdio.h>

int main(void) {
    union U {
        int i;
        float f;
    } u;

    u.i = 42;
    printf("as int: %d\n", u.i);

    u.f = 3.14f;
    printf("as float: %f\n", u.f);
    return 0;
}
Output:
as int: 42
as float: 3.140000
Example 2: Union size equals largest member
#include <stdio.h>

struct S {
    int i;
    float f;
};

union U {
    int i;
    float f;
};

int main(void) {
    printf("sizeof(struct S) = %zu\n", sizeof(struct S));
    printf("sizeof(union U)  = %zu\n", sizeof(union U));
    return 0;
}
Output:
sizeof(struct S) = 8
sizeof(union U) = 4
Example 3: Tagged union — remember which member is active
#include <stdio.h>

typedef enum { KIND_INT, KIND_FLOAT } Kind;

typedef struct {
    Kind kind;
    union {
        int i;
        float f;
    } u;
} Value;

int main(void) {
    Value v;
    v.kind = KIND_INT;
    v.u.i = 7;
    if (v.kind == KIND_INT)
        printf("%d\n", v.u.i);

    v.kind = KIND_FLOAT;
    v.u.f = 2.5f;
    if (v.kind == KIND_FLOAT)
        printf("%f\n", v.u.f);
    return 0;
}
Output:
7
2.500000
Example 4: View the same bits as int and float (type punning)
#include <stdio.h>
#include <stdint.h>

int main(void) {
    union {
        float f;
        uint32_t u;
    } x;

    x.f = 1.0f;
    printf("float %f has bits 0x%08X\n", x.f, x.u);
    return 0;
}
Output:
float 1.000000 has bits 0x3F800000
CRITICAL: Union Safety Rules
  1. Only one union member contains valid data at any time
  2. Reading from a member other than the last one written to causes undefined behavior
  3. Always keep track of which member is currently active
  4. Use tagged unions (struct with type field + union) for safety
  5. Unions can be used for type punning in C99 (legal), but be careful
  6. Initialize unions properly - only first member can be initialized in declaration
  7. Unions containing pointers need special care (alignment issues)

Structure Arrays - Complete Guide

Arrays of structures allow you to store multiple instances of a structure in contiguous memory. This is useful for managing collections of similar data, like student records, employee data, or inventory items.

Syntax:
// Array of structures struct StructureName array_name[size]; // Accessing elements array_name[index].member = value; // Dynamic array of structures struct StructureName *ptr = malloc(n * sizeof(struct StructureName));

Key Characteristics:

  • Contiguous Memory: All structures stored sequentially
  • Indexed Access: Use array indexing to access elements
  • Memory Layout: Predictable memory pattern
  • Efficient Processing: Can process all elements in loops
  • Dynamic Arrays: Can allocate at runtime using malloc/calloc
  • Multi-dimensional: Can create 2D arrays of structures

Structure Array Examples:

Short programs: static arrays, initialization, dynamic allocation, and a 2×2 grid.

Example 1: Array of structs — fill in a loop
#include <stdio.h>

struct Item {
    int id;
    int qty;
};

int main(void) {
    struct Item cart[3];
    for (int i = 0; i < 3; i++) {
        cart[i].id = i + 1;
        cart[i].qty = (i + 1) * 2;
    }
    for (int i = 0; i < 3; i++)
        printf("item %d: qty %d\n", cart[i].id, cart[i].qty);
    return 0;
}
Output:
item 1: qty 2
item 2: qty 4
item 3: qty 6
Example 2: Initialize an array of structs at declaration
#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main(void) {
    struct Point pts[] = {{0, 0}, {1, 2}, {3, 4}};
    int n = (int)(sizeof pts / sizeof pts[0]);
    for (int i = 0; i < n; i++)
        printf("(%d,%d)\n", pts[i].x, pts[i].y);
    return 0;
}
Output:
(0,0)
(1,2)
(3,4)
Example 3: Dynamic array with malloc
#include <stdio.h>
#include <stdlib.h>

struct Record {
    int key;
    double value;
};

int main(void) {
    int n = 3;
    struct Record *r = malloc((size_t)n * sizeof *r);
    if (!r) return 1;

    for (int i = 0; i < n; i++) {
        r[i].key = i;
        r[i].value = i * 1.5;
    }
    for (int i = 0; i < n; i++)
        printf("%d -> %.1f\n", r[i].key, r[i].value);

    free(r);
    return 0;
}
Output:
0 -> 0.0
1 -> 1.5
2 -> 3.0
Example 4: Find index of maximum (array of structs)
#include <stdio.h>

struct Score {
    char name[20];
    int points;
};

int main(void) {
    struct Score a[] = {
        {"Ann", 80},
        {"Ben", 95},
        {"Cal", 70}
    };
    int n = (int)(sizeof a / sizeof a[0]);
    int best = 0;
    for (int i = 1; i < n; i++)
        if (a[i].points > a[best].points)
            best = i;
    printf("top: %s (%d)\n", a[best].name, a[best].points);
    return 0;
}
Output:
top: Ben (95)
Example 5: Two-dimensional array of structs
#include <stdio.h>

struct Cell {
    int row;
    int col;
};

int main(void) {
    struct Cell grid[2][2];
    for (int r = 0; r < 2; r++)
        for (int c = 0; c < 2; c++) {
            grid[r][c].row = r;
            grid[r][c].col = c;
        }
    printf("[%d,%d] [%d,%d]\n",
           grid[0][0].row, grid[0][0].col,
           grid[1][1].row, grid[1][1].col);
    return 0;
}
Output:
[0,0] [1,1]

Pointers to Structures - Complete Guide

Pointers to structures allow dynamic allocation of structures, passing structures efficiently to functions (by reference), and creating complex data structures like linked lists and trees.

Syntax:
// Pointer to structure struct StructureName *ptr; // Dynamic allocation ptr = (struct StructureName*)malloc(sizeof(struct StructureName)); // Accessing members (*ptr).member = value; // Method 1 ptr->member = value; // Method 2 (arrow operator)

Key Concepts:

  • Arrow Operator (->): Shortcut for (*ptr).member
  • Dynamic Allocation: Create structures at runtime
  • Pass by Reference: Efficient function parameter passing
  • Linked Structures: Foundation for linked lists, trees
  • Memory Management: Must free allocated structures
  • Pointer Arithmetic: Works with arrays of structures

Pointer to Structure Examples:

Use -> instead of (*ptr).member. Check for NULL after malloc and free what you allocate.

Example 1: Address of a struct and the arrow operator
#include <stdio.h>

struct Box {
    int w;
    int h;
};

int main(void) {
    struct Box b = {4, 5};
    struct Box *p = &b;
    printf("(*p).w = %d, p->h = %d\n", (*p).w, p->h);
    p->w = 10;
    printf("b.w = %d\n", b.w);
    return 0;
}
Output:
(*p).w = 4, p->h = 5
b.w = 10
Example 2: One struct on the heap
#include <stdio.h>
#include <stdlib.h>

struct Point {
    int x;
    int y;
};

int main(void) {
    struct Point *p = malloc(sizeof *p);
    if (!p) return 1;
    p->x = 1;
    p->y = 2;
    printf("%d %d\n", p->x, p->y);
    free(p);
    return 0;
}
Output:
1 2
Example 3: Dynamic array of structs
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    double v;
} Row;

int main(void) {
    int n = 3;
    Row *r = malloc((size_t)n * sizeof *r);
    if (!r) return 1;

    for (int i = 0; i < n; i++) {
        r[i].id = i;
        r[i].v = i * 0.5;
    }
    Row *q = r;
    for (int i = 0; i < n; i++, q++)
        printf("%d: %.1f\n", q->id, q->v);

    free(r);
    return 0;
}
Output:
0: 0.0
1: 0.5
2: 1.0
Example 4: Modify through a pointer parameter
#include <stdio.h>

struct Counter {
    int n;
};

void bump(struct Counter *c) {
    c->n++;
}

int main(void) {
    struct Counter c = {0};
    bump(&c);
    bump(&c);
    printf("n = %d\n", c.n);
    return 0;
}
Output:
n = 2
Example 5: Simple linked list (three nodes)
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int value;
    struct Node *next;
} Node;

int main(void) {
    Node *a = malloc(sizeof *a);
    Node *b = malloc(sizeof *b);
    Node *c = malloc(sizeof *c);
    if (!a || !b || !c) return 1;

    a->value = 10;
    a->next = b;
    b->value = 20;
    b->next = c;
    c->value = 30;
    c->next = NULL;

    for (Node *p = a; p != NULL; p = p->next)
        printf("%d\n", p->value);

    free(a);
    free(b);
    free(c);
    return 0;
}
Output:
10
20
30

Nested Structures and Typedef

Nested structures allow you to create complex data types by including structures within other structures. The typedef keyword creates type aliases for easier structure usage.

Syntax:
// Nested structure struct Date { int day, month, year; }; struct Employee { int id; char name[50]; struct Date dob; // Nested structure struct Date joinDate; }; // Typedef for simpler syntax typedef struct Employee Employee; // Or combined: typedef struct { int id; char name[50]; } Person;

Key Concepts:

  • Nested Structures: Structure containing other structures
  • Multi-level Access: Use multiple dot operators: emp.dob.day
  • Typedef: Creates type alias for easier declaration
  • Self-Referential: Structures containing pointers to same type
  • Forward Declaration: Declare structure before defining for mutual references
  • Anonymous Structures: Structures without tag names

Nested Structures and Typedef Examples:

Nested members use extra dots: outer.inner.field. typedef gives a shorter type name.

Example 1: Struct inside struct (birth date)
#include <stdio.h>

struct Date {
    int day, month, year;
};

struct Person {
    int id;
    struct Date dob;
};

int main(void) {
    struct Person p;
    p.id = 1;
    p.dob.day = 5;
    p.dob.month = 3;
    p.dob.year = 2000;
    printf("id %d born %d/%d/%d\n",
           p.id, p.dob.day, p.dob.month, p.dob.year);
    return 0;
}
Output:
id 1 born 5/3/2000
Example 2: typedef — same layout, simpler name
#include <stdio.h>

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

int main(void) {
    Point a = {3, 4};
    printf("(%d,%d)\n", a.x, a.y);
    return 0;
}
Output:
(3,4)
Example 3: Two levels: address inside employee
#include <stdio.h>

struct Address {
    char city[32];
    int zip;
};

struct Employee {
    int id;
    struct Address home;
};

int main(void) {
    struct Employee e = {10, {"Austin", 78701}};
    printf("%d lives in %s %d\n", e.id, e.home.city, e.home.zip);
    return 0;
}
Output:
10 lives in Austin 78701
Example 4: Array member inside a typedef struct
#include <stdio.h>

typedef struct {
    int n;
    int scores[3];
} Team;

int main(void) {
    Team t = {1, {10, 20, 30}};
    int sum = 0;
    for (int i = 0; i < 3; i++)
        sum += t.scores[i];
    printf("team %d total %d\n", t.n, sum);
    return 0;
}
Output:
team 1 total 60
Example 5: Anonymous inner struct + list on the stack
#include <stdio.h>
#include <string.h>

typedef struct {
    int id;
    struct {
        char first[24];
        char last[24];
    } name;
} Person;

typedef struct Node {
    int v;
    struct Node *next;
} Node;

int main(void) {
    Person p;
    p.id = 7;
    strcpy(p.name.first, "Ann");
    strcpy(p.name.last, "Lee");
    printf("%s %s (id %d)\n", p.name.first, p.name.last, p.id);

    Node n3 = {30, NULL};
    Node n2 = {20, &n3};
    Node n1 = {10, &n2};
    for (Node *q = &n1; q != NULL; q = q->next)
        printf("%d\n", q->v);
    return 0;
}
Output:
Ann Lee (id 7)
10
20
30

Bit Fields and Advanced Topics

Bit fields allow you to specify the exact number of bits that a structure member should occupy. This is useful for memory optimization, hardware register access, and packing data efficiently.

Syntax:
struct { type member_name : width; // ... more members };

type: int, signed int, unsigned int (C99 allows _Bool)

width: Number of bits (1 to sizeof(type)*8)

Key Characteristics:

  • Memory Optimization: Pack multiple fields into single bytes
  • Hardware Access: Map to hardware register bit fields
  • Portability Issues: Implementation-dependent (order, padding)
  • Limited Types: Mostly integer types
  • No Address Operator: Cannot take address of bit field (&)
  • Alignment: Compiler may insert padding between bit fields

Bit Field Visualization:

32-bit Integer with Bit Fields
31
30
29
28
27
26
25
24
Sign (1 bit)
23
22
21
20
19
18
17
16
Exponent (8 bits)
15
14
13
12
11
10
9
8
Mantissa (23 bits)
7
6
5
4
3
2
1
0

IEEE 754 Single Precision Float (32 bits) using bit fields

Bit Field Examples:

Bit layout is implementation-defined; these snippets show typical uses. Do not take the address of a bit-field.

Example 1: Small flags in one unsigned int
#include <stdio.h>

struct Flags {
    unsigned int on : 1;
    unsigned int err : 1;
    unsigned int spare : 6;
};

int main(void) {
    struct Flags f = {0};
    f.on = 1;
    printf("on=%u err=%u\n", f.on, f.err);
    return 0;
}
Output:
on=1 err=0
Example 2: Signed small temperature, unsigned humidity
#include <stdio.h>

struct Reading {
    signed int temp_c : 8;
    unsigned int humidity : 8;
};

int main(void) {
    struct Reading r;
    r.temp_c = -5;
    r.humidity = 80;
    printf("temp %d C, humidity %u\n", r.temp_c, r.humidity);
    return 0;
}
Output:
temp -5 C, humidity 80
Example 3: RGB565-style packing (conceptual)
#include <stdio.h>

struct RGB565 {
    unsigned int b : 5;
    unsigned int g : 6;
    unsigned int r : 5;
};

int main(void) {
    struct RGB565 c;
    c.r = 31;
    c.g = 0;
    c.b = 0;
    printf("R=%u G=%u B=%u\n", c.r, c.g, c.b);
    return 0;
}
Output:
R=31 G=0 B=0
Example 4: Union of raw integer + bit-field view
#include <stdio.h>
#include <stdint.h>

int main(void) {
    union {
        uint8_t raw;
        struct {
            uint8_t lo : 4;
            uint8_t hi : 4;
        } n;
    } u;

    u.raw = 0;
    u.n.lo = 0xF;
    u.n.hi = 0x3;
    printf("raw = 0x%02X\n", u.raw);
    return 0;
}
Output:
raw = 0x3F(exact byte may depend on compiler bit-field ordering)
Example 5: Packed day / month / year (small ranges)
#include <stdio.h>

struct PackedDate {
    unsigned int day : 5;
    unsigned int month : 4;
    unsigned int year : 7;
};

int main(void) {
    struct PackedDate d;
    d.day = 28;
    d.month = 3;
    d.year = 26;
    printf("%u/%u/20%u\n", d.day, d.month, d.year);
    return 0;
}
Output:
28/3/2026
CRITICAL: Bit Field Limitations
  1. Implementation-defined: Order of bits, padding, alignment are compiler-dependent
  2. No addresses: Cannot take address of bit field with & operator
  3. Portability: Code with bit fields may not work across different compilers/platforms
  4. Limited types: Only int, signed int, unsigned int, _Bool (C99)
  5. Range checking: No automatic bounds checking - overflow wraps around
  6. Performance: May be slower due to bit manipulation
  7. Debugging: Harder to debug than regular variables
  8. Endianness: Bit order depends on endianness of platform

Common Mistakes and Best Practices

Common Mistake 1: Forgetting struct keyword
struct Point { int x, y; }; Point p; // ERROR: Need 'struct Point p' or typedef
Solution: Use typedef or always include struct typedef struct Point Point; // Now can use Point // OR struct Point p; // Always include struct keyword
Common Mistake 2: Comparing structures directly
struct Point p1 = {1, 2}, p2 = {1, 2}; if(p1 == p2) { ... } // ERROR: Cannot compare structures
Solution: Compare member by member if(p1.x == p2.x && p1.y == p2.y) { ... } // OR write comparison function int pointsEqual(struct Point a, struct Point b) { return a.x == b.x && a.y == b.y; }
Common Mistake 3: Reading wrong union member
union Data { int i; float f; } d; d.i = 42; printf("%f", d.f); // ERROR: Reading wrong member
Solution: Keep track of active member typedef enum { INT, FLOAT } DataType; typedef struct { DataType type; union { int i; float f; } data; } TaggedUnion; TaggedUnion d = {INT, {42}}; if(d.type == INT) printf("%d", d.data.i);
Common Mistake 4: Structure padding issues
struct Bad { char c; // 1 byte int i; // 4 bytes }; // sizeof = 8 (3 bytes padding)
Solution: Order members by size or use pragma pack struct Good { int i; // 4 bytes char c; // 1 byte }; // sizeof = 8 (3 bytes padding at end) #pragma pack(1) // Disable padding (use with caution) struct Packed { char c; int i; }; // sizeof = 5
Structure Best Practices:
  1. Use typedef: Makes code cleaner and easier to read
  2. Initialize structures: Always initialize, especially dynamic ones
  3. Order members: Place larger members first to minimize padding
  4. Document: Comment structure purpose and member meanings
  5. Use const: When structures shouldn't be modified
  6. Avoid deep nesting: Too many levels make code hard to read
  7. Create helper functions: For common operations (init, copy, compare)
  8. Consider memory layout: For performance-critical code
  9. Test on different platforms: If using bit fields or specific alignment
  10. Use standard naming: Consistent naming conventions
Union Best Practices:
  1. Use tagged unions: Always keep track of active member
  2. Clear before use: When switching between members, especially strings
  3. Document active member: Clearly document which member should be accessed
  4. Use for memory optimization: When you need to store different types at different times
  5. Avoid for general use: Structures are usually better for grouping data
  6. Be careful with pointers: Union members with pointers need special handling
  7. Test thoroughly: Unions can cause subtle bugs
  8. Consider alternatives: Sometimes void pointers are better

Key Takeaways

  • Structures (struct) group related data of different types with separate memory for each member
  • Unions (union) share memory between members - only one member is valid at a time
  • Use dot operator (.) for structure variables, arrow operator (->) for pointers to structures
  • Structure arrays allow storing multiple instances, useful for collections
  • Pointers to structures enable dynamic allocation and efficient function parameter passing
  • Nested structures create complex data types by including structures within structures
  • typedef creates type aliases for cleaner syntax: typedef struct { ... } TypeName;
  • Bit fields specify exact bit allocation for members, useful for memory optimization
  • Structures cannot be compared directly with == - must compare member by member
  • Structure padding may increase size - compilers add bytes for alignment
  • Use designated initializers for clearer structure initialization
  • Self-referential structures contain pointers to same type, used for linked lists and trees
  • Unions are useful for variant types, hardware registers, and memory-efficient storage
  • Always use tagged unions (struct with type field) to track active member
  • Bit fields have portability issues - implementation-defined behavior
  • Consider memory layout and alignment for performance-critical applications
Next Topics: We'll explore C file handling in detail, including file operations (open, read, write, close), file modes, binary vs text files, and file pointer manipulation.