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:

Example 1: Basic Structure Operations
#include <stdio.h>
#include <string.h>

int main() {
    printf("=== BASIC STRUCTURE OPERATIONS ===\n\n");
    
    // Structure declaration
    struct Student {
        int id;
        char name[50];
        float marks;
        char grade;
    };
    
    printf("1. Structure variable declaration and initialization:\n");
    
    // Method 1: Declare then initialize
    struct Student student1;
    student1.id = 101;
    strcpy(student1.name, "John Doe");
    student1.marks = 85.5;
    student1.grade = 'A';
    
    printf("   Student 1: ID=%d, Name=%s, Marks=%.1f, Grade=%c\n",
           student1.id, student1.name, student1.marks, student1.grade);
    
    // Method 2: Initialize during declaration
    struct Student student2 = {102, "Jane Smith", 92.0, 'A'};
    printf("   Student 2: ID=%d, Name=%s, Marks=%.1f, Grade=%c\n",
           student2.id, student2.name, student2.marks, student2.grade);
    
    // Method 3: Designated initializers (C99)
    struct Student student3 = {
        .id = 103,
        .name = "Bob Johnson",
        .marks = 78.5,
        .grade = 'B'
    };
    printf("   Student 3: ID=%d, Name=%s, Marks=%.1f, Grade=%c\n",
           student3.id, student3.name, student3.marks, student3.grade);
    
    printf("\n2. Structure size and alignment:\n");
    
    printf("   Size of Student structure: %zu bytes\n", sizeof(struct Student));
    printf("   Size of id (int): %zu bytes\n", sizeof(student1.id));
    printf("   Size of name (char[50]): %zu bytes\n", sizeof(student1.name));
    printf("   Size of marks (float): %zu bytes\n", sizeof(student1.marks));
    printf("   Size of grade (char): %zu bytes\n", sizeof(student1.grade));
    printf("   Note: Structure may have padding for alignment\n");
    
    printf("\n3. Structure assignment (member-wise copy):\n");
    
    struct Student student4;
    student4 = student1;  // Copy all members from student1 to student4
    
    // Modify student4
    student4.id = 104;
    strcpy(student4.name, "Modified Name");
    
    printf("   Original student1: %s (ID: %d)\n", student1.name, student1.id);
    printf("   Copied student4: %s (ID: %d)\n", student4.name, student4.id);
    printf("   Note: Assignment creates independent copy\n");
    
    printf("\n4. Structure as function argument:\n");
    
    // Function to display student
    void displayStudent(struct Student s) {
        printf("   Display: ID=%d, Name=%s, Marks=%.1f, Grade=%c\n",
               s.id, s.name, s.marks, s.grade);
    }
    
    displayStudent(student1);
    
    // Function to modify student (passed by value - creates copy)
    void modifyStudent(struct Student s) {
        s.id = 999;
        strcpy(s.name, "Modified in Function");
        printf("   Inside function: ID=%d, Name=%s\n", s.id, s.name);
    }
    
    modifyStudent(student1);
    printf("   After function (original unchanged): %s (ID: %d)\n", 
           student1.name, student1.id);
    
    printf("\n5. Returning structure from function:\n");
    
    // Function to create and return a student
    struct Student createStudent(int id, const char *name, float marks, char grade) {
        struct Student s;
        s.id = id;
        strcpy(s.name, name);
        s.marks = marks;
        s.grade = grade;
        return s;
    }
    
    struct Student student5 = createStudent(105, "Alice Brown", 88.5, 'A');
    printf("   Created student5: %s (ID: %d, Marks: %.1f)\n",
           student5.name, student5.id, student5.marks);
    
    printf("\n6. Structure comparison:\n");
    
    // Structures cannot be compared directly with ==
    // if(student1 == student2)  // ERROR: Invalid
    
    // Must compare member by member
    if(student1.id == student2.id && 
       strcmp(student1.name, student2.name) == 0 &&
       student1.marks == student2.marks &&
       student1.grade == student2.grade) {
        printf("   student1 and student2 are equal\n");
    } else {
        printf("   student1 and student2 are different\n");
    }
    
    printf("\n7. Anonymous structures (C11):\n");
    
    // Structure without tag name
    struct {
        int x;
        int y;
    } point1, point2;
    
    point1.x = 10;
    point1.y = 20;
    point2.x = 30;
    point2.y = 40;
    
    printf("   Anonymous structure points: (%d,%d) and (%d,%d)\n",
           point1.x, point1.y, point2.x, point2.y);
    
    return 0;
}
Output:
=== BASIC STRUCTURE OPERATIONS ===

1. Structure variable declaration and initialization:
Student 1: ID=101, Name=John Doe, Marks=85.5, Grade=A
Student 2: ID=102, Name=Jane Smith, Marks=92.0, Grade=A
Student 3: ID=103, Name=Bob Johnson, Marks=78.5, Grade=B

2. Structure size and alignment:
Size of Student structure: 60 bytes
Size of id (int): 4 bytes
Size of name (char[50]): 50 bytes
Size of marks (float): 4 bytes
Size of grade (char): 1 byte
Note: Structure may have padding for alignment

3. Structure assignment (member-wise copy):
Original student1: John Doe (ID: 101)
Copied student4: Modified Name (ID: 104)
Note: Assignment creates independent copy

4. Structure as function argument:
Display: ID=101, Name=John Doe, Marks=85.5, Grade=A
Inside function: ID=999, Name=Modified in Function
After function (original unchanged): John Doe (ID: 101)

5. Returning structure from function:
Created student5: Alice Brown (ID: 105, Marks: 88.5)

6. Structure comparison:
student1 and student2 are different

7. Anonymous structures (C11):
Anonymous structure points: (10,20) and (30,40)
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:

Example: Comprehensive Union Usage
#include <stdio.h>
#include <string.h>
#include <stdint.h>

int main() {
    printf("=== COMPREHENSIVE UNION USAGE ===\n\n");
    
    printf("1. Basic union example:\n");
    
    union Data {
        int i;
        float f;
        char str[20];
    };
    
    union Data data;
    
    printf("   Size of union Data: %zu bytes\n", sizeof(union Data));
    printf("   Size of int: %zu, float: %zu, char[20]: %zu\n",
           sizeof(int), sizeof(float), sizeof(char[20]));
    
    // Store integer
    data.i = 42;
    printf("   data.i = %d\n", data.i);
    printf("   data.f = %f (garbage - interpreting int as float)\n", data.f);
    
    // Store float (overwrites integer)
    data.f = 3.14159;
    printf("   After storing float:\n");
    printf("   data.f = %f\n", data.f);
    printf("   data.i = %d (garbage - interpreting float as int)\n", data.i);
    
    // Store string (overwrites float)
    strcpy(data.str, "Hello, Union!");
    printf("   After storing string:\n");
    printf("   data.str = %s\n", data.str);
    printf("   data.i = %d (garbage)\n", data.i);
    
    printf("\n2. Union vs Structure memory comparison:\n");
    
    struct StructData {
        int i;
        float f;
        char str[20];
    };
    
    union UnionData {
        int i;
        float f;
        char str[20];
    };
    
    printf("   Size of struct StructData: %zu bytes\n", sizeof(struct StructData));
    printf("   Size of union UnionData: %zu bytes\n", sizeof(union UnionData));
    printf("   Memory saved: %zu bytes\n", 
           sizeof(struct StructData) - sizeof(union UnionData));
    
    printf("\n3. Practical union use cases:\n");
    
    // Case 1: Variant type (store different types)
    typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } DataType;
    
    typedef struct {
        DataType type;
        union {
            int intValue;
            float floatValue;
            char stringValue[50];
        } data;
    } Variant;
    
    Variant var;
    
    var.type = TYPE_INT;
    var.data.intValue = 100;
    printf("   Variant as int: type=%d, value=%d\n", var.type, var.data.intValue);
    
    var.type = TYPE_FLOAT;
    var.data.floatValue = 3.14;
    printf("   Variant as float: type=%d, value=%.2f\n", var.type, var.data.floatValue);
    
    // Important: Clear string before use
    var.type = TYPE_STRING;
    memset(var.data.stringValue, 0, sizeof(var.data.stringValue));
    strcpy(var.data.stringValue, "Hello Variant");
    printf("   Variant as string: type=%d, value=%s\n", var.type, var.data.stringValue);
    
    printf("\n4. Type punning with unions (C99 legal):\n");
    
    union FloatPun {
        float f;
        uint32_t i;
    } pun;
    
    pun.f = 3.14159;
    printf("   Float value: %.5f\n", pun.f);
    printf("   Same bits as integer: 0x%08X\n", pun.i);
    
    // Extract sign, exponent, mantissa
    uint32_t sign = (pun.i >> 31) & 1;
    uint32_t exponent = (pun.i >> 23) & 0xFF;
    uint32_t mantissa = pun.i & 0x7FFFFF;
    
    printf("   IEEE 754 representation:\n");
    printf("   Sign: %u (%s)\n", sign, sign ? "negative" : "positive");
    printf("   Exponent: %u (biased), %d (unbiased)\n", exponent, exponent - 127);
    printf("   Mantissa: 0x%06X\n", mantissa);
    
    printf("\n5. Hardware register access (common in embedded systems):\n");
    
    // Simulating a hardware control register
    union ControlRegister {
        uint32_t raw;
        struct {
            uint32_t enable : 1;
            uint32_t mode : 3;
            uint32_t speed : 4;
            uint32_t reserved : 24;
        } bits;
    } ctrl;
    
    ctrl.raw = 0;
    ctrl.bits.enable = 1;
    ctrl.bits.mode = 5;
    ctrl.bits.speed = 9;
    
    printf("   Control register:\n");
    printf("   Raw value: 0x%08X\n", ctrl.raw);
    printf("   Bits - Enable: %u, Mode: %u, Speed: %u\n",
           ctrl.bits.enable, ctrl.bits.mode, ctrl.bits.speed);
    
    printf("\n6. Network packet parsing:\n");
    
    // Simulating network packet with different types
    typedef enum { PACKET_TYPE_A, PACKET_TYPE_B } PacketType;
    
    typedef struct {
        PacketType type;
        uint16_t length;
        union {
            struct {
                uint8_t command;
                uint16_t param1;
                uint32_t param2;
            } typeA;
            struct {
                uint32_t data[4];
                uint8_t flags;
            } typeB;
        } payload;
    } NetworkPacket;
    
    NetworkPacket packet;
    
    // Parse as Type A
    packet.type = PACKET_TYPE_A;
    packet.length = sizeof(packet.payload.typeA);
    packet.payload.typeA.command = 0x10;
    packet.payload.typeA.param1 = 0xABCD;
    packet.payload.typeA.param2 = 0x12345678;
    
    printf("   Packet Type A:\n");
    printf("   Command: 0x%02X, Param1: 0x%04X, Param2: 0x%08X\n",
           packet.payload.typeA.command,
           packet.payload.typeA.param1,
           packet.payload.typeA.param2);
    
    // Same memory, now interpret as Type B
    printf("\n   Same memory as Type B (garbage):\n");
    printf("   Data[0]: 0x%08X, Flags: 0x%02X\n",
           packet.payload.typeB.data[0],
           packet.payload.typeB.flags);
    
    printf("\n7. Union arrays for mixed data storage:\n");
    
    union MixedArray {
        int intArray[10];
        float floatArray[10];
        char charArray[40];
    } mixed;
    
    // Store as integers
    for(int i = 0; i < 10; i++) {
        mixed.intArray[i] = i * 10;
    }
    
    printf("   As integers: ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", mixed.intArray[i]);
    }
    printf("...\n");
    
    // Same memory as floats
    printf("   Same as floats (garbage): ");
    for(int i = 0; i < 5; i++) {
        printf("%.1f ", mixed.floatArray[i]);
    }
    printf("...\n");
    
    printf("\n8. Important union warnings:\n");
    
    // WARNING 1: Reading wrong member
    union Data d;
    d.i = 65;
    printf("   Setting d.i = 65\n");
    printf("   Reading d.str = %s (may work if ASCII 'A' then null)\n", d.str);
    
    // WARNING 2: Only last written member is valid
    union Data d2;
    d2.i = 100;
    d2.f = 3.14;  // Overwrites the integer
    // d2.i is now invalid!
    
    printf("   d2.i = %d (invalid after d2.f = 3.14)\n", d2.i);
    
    // WARNING 3: Union in structures need careful handling
    typedef struct {
        int type;
        union {
            int x;
            float y;
        } value;
    } TaggedUnion;
    
    printf("\n   Always: Keep track of which union member is active!\n");
    
    return 0;
}
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:

Example: Comprehensive Structure Array Usage
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// Student structure
typedef struct {
    int id;
    char name[50];
    float marks[5];  // Array inside structure
    float average;
    char grade;
} Student;

// Function prototypes
void printStudent(const Student *s);
float calculateAverage(const float marks[], int count);
char calculateGrade(float average);
void sortStudentsById(Student students[], int count);
void sortStudentsByAverage(Student students[], int count);

int main() {
    printf("=== COMPREHENSIVE STRUCTURE ARRAY USAGE ===\n\n");
    
    printf("1. Static array of structures:\n");
    
    // Array of 5 students
    Student class[5];
    
    // Initialize students
    for(int i = 0; i < 5; i++) {
        class[i].id = 1000 + i;
        sprintf(class[i].name, "Student%d", i + 1);
        
        // Initialize marks
        for(int j = 0; j < 5; j++) {
            class[i].marks[j] = 60 + (i * 5) + j;
        }
        
        // Calculate average and grade
        class[i].average = calculateAverage(class[i].marks, 5);
        class[i].grade = calculateGrade(class[i].average);
    }
    
    printf("   Student records:\n");
    for(int i = 0; i < 5; i++) {
        printf("   ");
        printStudent(&class[i]);
    }
    
    printf("\n2. Array of structures with initialization:\n");
    
    // Initialize array during declaration
    Student initializedClass[] = {
        {2001, "Alice", {85, 90, 88, 92, 87}, 0, ' '},
        {2002, "Bob", {78, 82, 79, 85, 80}, 0, ' '},
        {2003, "Charlie", {92, 95, 88, 90, 93}, 0, ' '}
    };
    
    // Calculate averages and grades
    int count = sizeof(initializedClass) / sizeof(initializedClass[0]);
    for(int i = 0; i < count; i++) {
        initializedClass[i].average = calculateAverage(initializedClass[i].marks, 5);
        initializedClass[i].grade = calculateGrade(initializedClass[i].average);
    }
    
    printf("   Initialized class (%d students):\n", count);
    for(int i = 0; i < count; i++) {
        printf("   ");
        printStudent(&initializedClass[i]);
    }
    
    printf("\n3. Dynamic array of structures:\n");
    
    int dynamicCount;
    printf("   Enter number of students: ");
    scanf("%d", &dynamicCount);
    
    // Allocate dynamic array
    Student *dynamicClass = (Student*)malloc(dynamicCount * sizeof(Student));
    
    if(dynamicClass == NULL) {
        printf("   Memory allocation failed!\n");
        return 1;
    }
    
    // Initialize dynamic array
    for(int i = 0; i < dynamicCount; i++) {
        dynamicClass[i].id = 3000 + i;
        sprintf(dynamicClass[i].name, "DynamicStudent%d", i + 1);
        
        for(int j = 0; j < 5; j++) {
            dynamicClass[i].marks[j] = 70 + (i * 3) + j;
        }
        
        dynamicClass[i].average = calculateAverage(dynamicClass[i].marks, 5);
        dynamicClass[i].grade = calculateGrade(dynamicClass[i].average);
    }
    
    printf("   Dynamic class (%d students):\n", dynamicCount);
    for(int i = 0; i < dynamicCount && i < 3; i++) {  // Show first 3
        printf("   ");
        printStudent(&dynamicClass[i]);
    }
    if(dynamicCount > 3) {
        printf("   ... and %d more\n", dynamicCount - 3);
    }
    
    printf("\n4. Sorting array of structures:\n");
    
    // Sort by ID
    sortStudentsById(class, 5);
    printf("   Sorted by ID:\n");
    for(int i = 0; i < 5; i++) {
        printf("   ID: %d, Name: %s\n", class[i].id, class[i].name);
    }
    
    // Sort by average
    sortStudentsByAverage(class, 5);
    printf("\n   Sorted by average (highest first):\n");
    for(int i = 0; i < 5; i++) {
        printf("   %s: %.2f (%c)\n", 
               class[i].name, class[i].average, class[i].grade);
    }
    
    printf("\n5. 2D array of structures:\n");
    
    // 3 classes, 4 students each
    Student school[3][4];
    
    // Initialize 2D array
    for(int classNum = 0; classNum < 3; classNum++) {
        for(int studentNum = 0; studentNum < 4; studentNum++) {
            school[classNum][studentNum].id = 4000 + (classNum * 100) + studentNum;
            sprintf(school[classNum][studentNum].name, 
                   "C%d_S%d", classNum + 1, studentNum + 1);
            
            for(int mark = 0; mark < 5; mark++) {
                school[classNum][studentNum].marks[mark] = 
                    60 + (classNum * 10) + (studentNum * 5) + mark;
            }
            
            school[classNum][studentNum].average = 
                calculateAverage(school[classNum][studentNum].marks, 5);
            school[classNum][studentNum].grade = 
                calculateGrade(school[classNum][studentNum].average);
        }
    }
    
    printf("   2D array (3 classes x 4 students):\n");
    for(int classNum = 0; classNum < 3; classNum++) {
        printf("   Class %d:\n", classNum + 1);
        for(int studentNum = 0; studentNum < 4; studentNum++) {
            printf("     ");
            printStudent(&school[classNum][studentNum]);
        }
    }
    
    printf("\n6. Array of structures with pointers:\n");
    
    // Array of pointers to structures
    Student *studentPtrs[5];
    
    for(int i = 0; i < 5; i++) {
        studentPtrs[i] = &class[i];  // Point to existing structures
    }
    
    printf("   Array of pointers to students:\n");
    for(int i = 0; i < 5; i++) {
        printf("   Pointer[%d] -> ", i);
        printStudent(studentPtrs[i]);
    }
    
    // Modify through pointer
    studentPtrs[0]->id = 9999;
    strcpy(studentPtrs[0]->name, "Modified Through Pointer");
    
    printf("\n   After modification through pointer:\n");
    printf("   Original: ");
    printStudent(&class[0]);
    
    printf("\n7. Passing structure array to functions:\n");
    
    // Function to find student with highest average
    Student* findTopStudent(Student students[], int count) {
        if(count <= 0) return NULL;
        
        Student *top = &students[0];
        for(int i = 1; i < count; i++) {
            if(students[i].average > top->average) {
                top = &students[i];
            }
        }
        return top;
    }
    
    Student *topStudent = findTopStudent(class, 5);
    if(topStudent != NULL) {
        printf("   Top student: ");
        printStudent(topStudent);
    }
    
    // Function to count students by grade
    int countByGrade(Student students[], int count, char grade) {
        int counter = 0;
        for(int i = 0; i < count; i++) {
            if(students[i].grade == grade) {
                counter++;
            }
        }
        return counter;
    }
    
    printf("   Grade distribution:\n");
    char grades[] = {'A', 'B', 'C', 'D', 'F'};
    for(int i = 0; i < 5; i++) {
        int count = countByGrade(class, 5, grades[i]);
        printf("   Grade %c: %d students\n", grades[i], count);
    }
    
    printf("\n8. Array of unions:\n");
    
    union Value {
        int intVal;
        float floatVal;
        char strVal[20];
    };
    
    union Value values[5];
    
    // Store different types in array
    values[0].intVal = 100;
    values[1].floatVal = 3.14;
    strcpy(values[2].strVal, "Hello");
    values[3].intVal = 200;
    values[4].floatVal = 2.718;
    
    printf("   Array of unions (interpreted as integers):\n   ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", values[i].intVal);
    }
    printf("\n   Note: Only some values are valid as integers\n");
    
    // Free dynamic memory
    free(dynamicClass);
    dynamicClass = NULL;
    
    return 0;
}

// Function implementations
void printStudent(const Student *s) {
    printf("ID: %4d, Name: %-15s, Avg: %5.2f, Grade: %c\n",
           s->id, s->name, s->average, s->grade);
}

float calculateAverage(const float marks[], int count) {
    float sum = 0;
    for(int i = 0; i < count; i++) {
        sum += marks[i];
    }
    return sum / count;
}

char calculateGrade(float average) {
    if(average >= 90) return 'A';
    if(average >= 80) return 'B';
    if(average >= 70) return 'C';
    if(average >= 60) return 'D';
    return 'F';
}

void sortStudentsById(Student students[], int count) {
    for(int i = 0; i < count - 1; i++) {
        for(int j = 0; j < count - i - 1; j++) {
            if(students[j].id > students[j + 1].id) {
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
}

void sortStudentsByAverage(Student students[], int count) {
    for(int i = 0; i < count - 1; i++) {
        for(int j = 0; j < count - i - 1; j++) {
            if(students[j].average < students[j + 1].average) {
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
}

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:

Example: Comprehensive Pointer to Structure Usage
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Basic structure
typedef struct {
    int id;
    char name[50];
    float salary;
} Employee;

// Linked list node
typedef struct Node {
    Employee data;
    struct Node *next;
} Node;

// Binary tree node
typedef struct TreeNode {
    Employee data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

// Function prototypes
void printEmployee(const Employee *emp);
Employee* createEmployee(int id, const char *name, float salary);
void modifyEmployee(Employee *emp, float newSalary);
Node* createNode(const Employee *emp);
void printList(Node *head);
TreeNode* createTreeNode(const Employee *emp);
void printTree(TreeNode *root);

int main() {
    printf("=== COMPREHENSIVE POINTER TO STRUCTURE USAGE ===\n\n");
    
    printf("1. Basic pointer to structure:\n");
    
    Employee emp1 = {101, "John Doe", 50000.0};
    Employee *ptr = &emp1;
    
    printf("   Accessing through pointer:\n");
    printf("   Using (*ptr). : ID=%d, Name=%s, Salary=%.2f\n",
           (*ptr).id, (*ptr).name, (*ptr).salary);
    printf("   Using ptr->   : ID=%d, Name=%s, Salary=%.2f\n",
           ptr->id, ptr->name, ptr->salary);
    
    // Modify through pointer
    ptr->salary = 55000.0;
    strcpy(ptr->name, "John Smith");
    printf("   After modification: %s, $%.2f\n", emp1.name, emp1.salary);
    
    printf("\n2. Dynamic structure allocation:\n");
    
    // Single dynamic structure
    Employee *dynamicEmp = (Employee*)malloc(sizeof(Employee));
    
    if(dynamicEmp == NULL) {
        printf("   Memory allocation failed!\n");
        return 1;
    }
    
    dynamicEmp->id = 102;
    strcpy(dynamicEmp->name, "Jane Smith");
    dynamicEmp->salary = 60000.0;
    
    printf("   Dynamic employee: ");
    printEmployee(dynamicEmp);
    
    // Free single dynamic structure
    free(dynamicEmp);
    dynamicEmp = NULL;
    
    printf("\n3. Dynamic array of structures:\n");
    
    int count = 3;
    Employee *empArray = (Employee*)malloc(count * sizeof(Employee));
    
    if(empArray == NULL) {
        printf("   Array allocation failed!\n");
        return 1;
    }
    
    // Initialize array
    for(int i = 0; i < count; i++) {
        empArray[i].id = 200 + i;
        sprintf(empArray[i].name, "Employee%d", i + 1);
        empArray[i].salary = 45000.0 + (i * 5000);
    }
    
    printf("   Dynamic array:\n");
    for(int i = 0; i < count; i++) {
        printf("   ");
        printEmployee(&empArray[i]);
    }
    
    // Pointer arithmetic with structure array
    Employee *current = empArray;
    printf("\n   Using pointer arithmetic:\n");
    for(int i = 0; i < count; i++) {
        printf("   ");
        printEmployee(current);
        current++;  // Moves to next structure
    }
    
    free(empArray);
    empArray = NULL;
    
    printf("\n4. Functions with structure pointers:\n");
    
    Employee *newEmp = createEmployee(301, "Bob Johnson", 48000.0);
    if(newEmp != NULL) {
        printf("   Created via function: ");
        printEmployee(newEmp);
        
        modifyEmployee(newEmp, 52000.0);
        printf("   After raise: ");
        printEmployee(newEmp);
        
        free(newEmp);
        newEmp = NULL;
    }
    
    printf("\n5. Linked list of structures:\n");
    
    // Create employees
    Employee emp2 = {401, "Alice Brown", 55000};
    Employee emp3 = {402, "Charlie White", 60000};
    Employee emp4 = {403, "Diana Green", 52000};
    
    // Create linked list
    Node *head = createNode(&emp1);
    head->next = createNode(&emp2);
    head->next->next = createNode(&emp3);
    head->next->next->next = createNode(&emp4);
    
    printf("   Linked list of employees:\n");
    printList(head);
    
    // Free linked list
    Node *temp;
    while(head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
    
    printf("\n6. Binary tree of structures:\n");
    
    // Create tree nodes
    TreeNode *root = createTreeNode(&emp1);
    root->left = createTreeNode(&emp2);
    root->right = createTreeNode(&emp3);
    root->left->left = createTreeNode(&emp4);
    
    printf("   Binary tree (in-order):\n");
    printTree(root);
    
    // Free tree (simplified - would need proper traversal)
    free(root->left->left);
    free(root->left);
    free(root->right);
    free(root);
    
    printf("\n7. Array of pointers to structures:\n");
    
    Employee employees[3] = {
        {501, "Eve Black", 47000},
        {502, "Frank Blue", 53000},
        {503, "Grace Red", 49000}
    };
    
    // Array of pointers
    Employee *empPtrs[3];
    for(int i = 0; i < 3; i++) {
        empPtrs[i] = &employees[i];
    }
    
    printf("   Array of pointers:\n");
    for(int i = 0; i < 3; i++) {
        printf("   ");
        printEmployee(empPtrs[i]);
    }
    
    // Sort array of pointers (without moving structures)
    for(int i = 0; i < 2; i++) {
        for(int j = 0; j < 2 - i; j++) {
            if(empPtrs[j]->salary > empPtrs[j + 1]->salary) {
                Employee *tempPtr = empPtrs[j];
                empPtrs[j] = empPtrs[j + 1];
                empPtrs[j + 1] = tempPtr;
            }
        }
    }
    
    printf("\n   Sorted by salary (pointers only):\n");
    for(int i = 0; i < 3; i++) {
        printf("   ");
        printEmployee(empPtrs[i]);
    }
    
    printf("\n   Original array unchanged:\n");
    for(int i = 0; i < 3; i++) {
        printf("   ");
        printEmployee(&employees[i]);
    }
    
    printf("\n8. Pointer to pointer to structure:\n");
    
    Employee emp5 = {601, "Henry Ford", 65000};
    Employee *singlePtr = &emp5;
    Employee **doublePtr = &singlePtr;
    
    printf("   Double pointer access:\n");
    printf("   (*doublePtr)->id = %d\n", (*doublePtr)->id);
    printf("   (**doublePtr).id = %d\n", (**doublePtr).id);
    
    // Modify through double pointer
    (**doublePtr).salary = 70000;
    printf("   After double pointer modification: $%.2f\n", emp5.salary);
    
    printf("\n9. Self-referential structures (linked list):\n");
    
    typedef struct ListNode {
        int data;
        struct ListNode *next;
    } ListNode;
    
    // Create simple linked list
    ListNode *node1 = (ListNode*)malloc(sizeof(ListNode));
    ListNode *node2 = (ListNode*)malloc(sizeof(ListNode));
    ListNode *node3 = (ListNode*)malloc(sizeof(ListNode));
    
    node1->data = 10;
    node1->next = node2;
    
    node2->data = 20;
    node2->next = node3;
    
    node3->data = 30;
    node3->next = NULL;
    
    printf("   Self-referential linked list:\n");
    ListNode *currentNode = node1;
    while(currentNode != NULL) {
        printf("   Node data: %d\n", currentNode->data);
        currentNode = currentNode->next;
    }
    
    free(node1);
    free(node2);
    free(node3);
    
    printf("\n10. Common pointer to structure errors:\n");
    
    // ERROR 1: Dereferencing NULL pointer
    Employee *nullPtr = NULL;
    // nullPtr->id = 100;  // CRASH: Segmentation fault
    
    // ERROR 2: Using pointer after free
    Employee *freedPtr = (Employee*)malloc(sizeof(Employee));
    freedPtr->id = 100;
    free(freedPtr);
    // freedPtr->id = 200;  // ERROR: Use after free
    
    // ERROR 3: Memory leak
    // Employee *leak = malloc(sizeof(Employee));
    // Forgot to free(leak);
    
    printf("   Always: Check for NULL, Free memory, Set to NULL after free\n");
    
    return 0;
}

// Function implementations
void printEmployee(const Employee *emp) {
    printf("ID: %d, Name: %-15s, Salary: $%.2f\n",
           emp->id, emp->name, emp->salary);
}

Employee* createEmployee(int id, const char *name, float salary) {
    Employee *emp = (Employee*)malloc(sizeof(Employee));
    if(emp == NULL) return NULL;
    
    emp->id = id;
    strcpy(emp->name, name);
    emp->salary = salary;
    
    return emp;
}

void modifyEmployee(Employee *emp, float newSalary) {
    if(emp == NULL) return;
    emp->salary = newSalary;
}

Node* createNode(const Employee *emp) {
    Node *node = (Node*)malloc(sizeof(Node));
    if(node == NULL) return NULL;
    
    node->data = *emp;  // Copy the employee data
    node->next = NULL;
    
    return node;
}

void printList(Node *head) {
    Node *current = head;
    int position = 1;
    
    while(current != NULL) {
        printf("   %d. ", position++);
        printEmployee(¤t->data);
        current = current->next;
    }
}

TreeNode* createTreeNode(const Employee *emp) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    if(node == NULL) return NULL;
    
    node->data = *emp;
    node->left = NULL;
    node->right = NULL;
    
    return node;
}

void printTree(TreeNode *root) {
    if(root == NULL) return;
    
    printTree(root->left);
    printf("   ");
    printEmployee(&root->data);
    printTree(root->right);
}

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:

Example: Comprehensive Nested Structures and Typedef
#include <stdio.h>
#include <string.h>

int main() {
    printf("=== COMPREHENSIVE NESTED STRUCTURES AND TYPEDEF ===\n\n");
    
    printf("1. Basic nested structures:\n");
    
    // Date structure
    struct Date {
        int day;
        int month;
        int year;
    };
    
    // Person structure with nested Date
    struct Person {
        int id;
        char name[50];
        struct Date birthDate;  // Nested structure
    };
    
    struct Person person1;
    person1.id = 101;
    strcpy(person1.name, "John Doe");
    person1.birthDate.day = 15;
    person1.birthDate.month = 6;
    person1.birthDate.year = 1990;
    
    printf("   Person: %s (ID: %d)\n", person1.name, person1.id);
    printf("   Birth Date: %d/%d/%d\n", 
           person1.birthDate.day, 
           person1.birthDate.month, 
           person1.birthDate.year);
    
    printf("\n2. Multiple level nesting:\n");
    
    struct Address {
        char street[50];
        char city[30];
        char state[20];
        int zipCode;
    };
    
    struct Contact {
        char email[50];
        char phone[15];
        struct Address address;  // Nested Address
    };
    
    struct Employee {
        int empId;
        char name[50];
        struct Contact contact;  // Nested Contact
        struct Date joinDate;    // Nested Date
    };
    
    struct Employee emp1 = {
        1001,
        "Alice Smith",
        {"alice@company.com", "123-456-7890", 
         {"123 Main St", "New York", "NY", 10001}},
        {10, 3, 2020}
    };
    
    printf("   Employee: %s (ID: %d)\n", emp1.name, emp1.empId);
    printf("   Email: %s, Phone: %s\n", emp1.contact.email, emp1.contact.phone);
    printf("   Address: %s, %s, %s %d\n",
           emp1.contact.address.street,
           emp1.contact.address.city,
           emp1.contact.address.state,
           emp1.contact.address.zipCode);
    printf("   Join Date: %d/%d/%d\n",
           emp1.joinDate.day, emp1.joinDate.month, emp1.joinDate.year);
    
    printf("\n3. Using typedef for cleaner syntax:\n");
    
    // Method 1: Declare struct then typedef
    struct Point {
        int x;
        int y;
    };
    typedef struct Point Point;  // Now can use just 'Point'
    
    // Method 2: Combine declaration and typedef
    typedef struct {
        float real;
        float imag;
    } Complex;
    
    // Method 3: Typedef with tag (less common)
    typedef struct Rectangle {
        int width;
        int height;
    } Rectangle;
    
    // Usage
    Point p1 = {10, 20};
    Complex c1 = {3.5, 2.8};
    Rectangle r1 = {100, 50};
    
    printf("   Point: (%d, %d)\n", p1.x, p1.y);
    printf("   Complex: %.1f + %.1fi\n", c1.real, c1.imag);
    printf("   Rectangle: %d x %d (area: %d)\n", 
           r1.width, r1.height, r1.width * r1.height);
    
    printf("\n4. Arrays within structures:\n");
    
    typedef struct {
        int studentId;
        char name[50];
        float grades[5];  // Array inside structure
        float average;
    } Student;
    
    Student student1;
    student1.studentId = 2001;
    strcpy(student1.name, "Bob Johnson");
    
    // Initialize grades array
    for(int i = 0; i < 5; i++) {
        student1.grades[i] = 70.0 + (i * 5);
    }
    
    // Calculate average
    float sum = 0;
    for(int i = 0; i < 5; i++) {
        sum += student1.grades[i];
    }
    student1.average = sum / 5;
    
    printf("   Student: %s (ID: %d)\n", student1.name, student1.studentId);
    printf("   Grades: ");
    for(int i = 0; i < 5; i++) {
        printf("%.1f ", student1.grades[i]);
    }
    printf("\n   Average: %.2f\n", student1.average);
    
    printf("\n5. Structure containing pointer to another structure:\n");
    
    typedef struct Node {
        int data;
        struct Node *next;  // Pointer to same type
    } Node;
    
    // Create simple linked list
    Node node1 = {10, NULL};
    Node node2 = {20, NULL};
    Node node3 = {30, NULL};
    
    node1.next = &node2;
    node2.next = &node3;
    
    printf("   Linked list:\n");
    Node *current = &node1;
    while(current != NULL) {
        printf("   Node data: %d\n", current->data);
        current = current->next;
    }
    
    printf("\n6. Mutual referencing structures (forward declaration):\n");
    
    // Forward declaration
    typedef struct Class Class;
    
    typedef struct StudentNode {
        char name[50];
        Class *class;  // Pointer to Class (not defined yet)
        struct StudentNode *next;
    } StudentNode;
    
    struct Class {
        char className[30];
        StudentNode *students;  // Pointer to StudentNode
        int studentCount;
    };
    
    // Create instances
    Class mathClass;
    strcpy(mathClass.className, "Mathematics");
    mathClass.studentCount = 0;
    mathClass.students = NULL;
    
    StudentNode studentA = {"Alice", &mathClass, NULL};
    StudentNode studentB = {"Bob", &mathClass, NULL};
    
    printf("   Class: %s\n", mathClass.className);
    printf("   Student A: %s (in class: %s)\n", 
           studentA.name, studentA.class->className);
    
    printf("\n7. Anonymous structures within structures:\n");
    
    typedef struct {
        int id;
        // Anonymous structure
        struct {
            char first[30];
            char last[30];
        } name;  // No tag name, just variable
        float salary;
    } Employee2;
    
    Employee2 emp2;
    emp2.id = 3001;
    strcpy(emp2.name.first, "John");
    strcpy(emp2.name.last, "Doe");
    emp2.salary = 55000.0;
    
    printf("   Employee: %s %s (ID: %d, Salary: $%.2f)\n",
           emp2.name.first, emp2.name.last, emp2.id, emp2.salary);
    
    printf("\n8. Typedef with function pointers in structures:\n");
    
    typedef double (*MathFunc)(double);
    
    typedef struct {
        char operation[20];
        MathFunc func;
    } MathOperation;
    
    // Math functions
    double square(double x) { return x * x; }
    double cube(double x) { return x * x * x; }
    
    MathOperation ops[] = {
        {"Square", square},
        {"Cube", cube}
    };
    
    printf("   Math operations:\n");
    for(int i = 0; i < 2; i++) {
        double result = ops[i].func(3.0);
        printf("   %s(3) = %.2f\n", ops[i].operation, result);
    }
    
    printf("\n9. Complex nested structure with typedef:\n");
    
    // Define types
    typedef struct { int x, y; } Point2D;
    typedef struct { Point2D start, end; } Line;
    typedef struct { Line edges[4]; } Rectangle2D;
    
    // Create rectangle
    Rectangle2D rect = {
        .edges = {
            {{0, 0}, {10, 0}},   // Top edge
            {{10, 0}, {10, 5}},  // Right edge
            {{10, 5}, {0, 5}},   // Bottom edge
            {{0, 5}, {0, 0}}     // Left edge
        }
    };
    
    printf("   Rectangle coordinates:\n");
    for(int i = 0; i < 4; i++) {
        printf("   Edge %d: (%d,%d) to (%d,%d)\n", i + 1,
               rect.edges[i].start.x, rect.edges[i].start.y,
               rect.edges[i].end.x, rect.edges[i].end.y);
    }
    
    printf("\n10. Benefits of typedef:\n");
    
    printf("   • Cleaner syntax: 'Employee' vs 'struct Employee'\n");
    printf("   • Easier to change underlying type\n");
    printf("   • Better code readability\n");
    printf("   • Consistent naming conventions\n");
    printf("   • Forward declarations easier\n");
    
    return 0;
}

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:

Example: Comprehensive Bit Field Usage
#include <stdio.h>
#include <stdint.h>

int main() {
    printf("=== COMPREHENSIVE BIT FIELD USAGE ===\n\n");
    
    printf("1. Basic bit fields:\n");
    
    struct StatusRegister {
        unsigned int error : 1;      // 1 bit for error flag
        unsigned int ready : 1;      // 1 bit for ready flag
        unsigned int busy : 1;       // 1 bit for busy flag
        unsigned int reserved : 5;   // 5 reserved bits
        unsigned int mode : 4;       // 4 bits for mode
        unsigned int data : 8;       // 8 bits for data
    };
    
    struct StatusRegister status;
    
    printf("   Size of StatusRegister: %zu bytes\n", sizeof(struct StatusRegister));
    printf("   Expected: 3 bytes (1+1+1+5+4+8 = 20 bits)\n");
    printf("   Note: May be larger due to padding/alignment\n");
    
    // Set values
    status.error = 0;
    status.ready = 1;
    status.busy = 0;
    status.mode = 5;
    status.data = 0xAB;
    
    printf("   Status: error=%u, ready=%u, busy=%u, mode=%u, data=0x%02X\n",
           status.error, status.ready, status.busy, status.mode, status.data);
    
    printf("\n2. Signed bit fields:\n");
    
    struct SignedFields {
        signed int temperature : 10;  // 10-bit signed (-512 to 511)
        unsigned int pressure : 12;   // 12-bit unsigned (0 to 4095)
        int : 0;                      // Force alignment to next boundary
        unsigned int humidity : 8;    // 8-bit unsigned (0 to 255)
    };
    
    struct SignedFields sensor;
    sensor.temperature = -25;    // Negative value in bit field
    sensor.pressure = 1023;
    sensor.humidity = 65;
    
    printf("   Sensor: temp=%d°C, pressure=%u hPa, humidity=%u%%\n",
           sensor.temperature, sensor.pressure, sensor.humidity);
    printf("   Size: %zu bytes\n", sizeof(struct SignedFields));
    
    printf("\n3. Anonymous bit fields for padding:\n");
    
    struct PaddedRegister {
        unsigned int flag1 : 1;
        unsigned int       : 3;  // Anonymous - 3 bits padding
        unsigned int flag2 : 1;
        unsigned int       : 0;  // Zero-width - force to next boundary
        unsigned int data  : 16;
    };
    
    struct PaddedRegister reg;
    printf("   Size with padding: %zu bytes\n", sizeof(struct PaddedRegister));
    
    printf("\n4. Hardware register simulation:\n");
    
    // Simulating a UART control register
    union UARTControl {
        uint16_t raw;
        struct {
            uint16_t enable : 1;
            uint16_t parity : 2;      // 00=none, 01=odd, 10=even
            uint16_t stopBits : 1;    // 0=1 bit, 1=2 bits
            uint16_t dataBits : 2;    // 00=5, 01=6, 10=7, 11=8
            uint16_t baudRate : 3;    // 8 possible baud rates
            uint16_t interrupt : 1;
            uint16_t : 6;             // Reserved bits
        } bits;
    };
    
    union UARTControl uart;
    uart.raw = 0;
    
    // Configure UART
    uart.bits.enable = 1;
    uart.bits.parity = 2;      // Even parity
    uart.bits.stopBits = 1;    // 2 stop bits
    uart.bits.dataBits = 3;    // 8 data bits
    uart.bits.baudRate = 4;    // 9600 baud
    uart.bits.interrupt = 1;
    
    printf("   UART Configuration:\n");
    printf("   Raw register: 0x%04X\n", uart.raw);
    printf("   Bits - Enable: %u, Parity: %u, Stop: %u, Data: %u, Baud: %u, Int: %u\n",
           uart.bits.enable, uart.bits.parity, uart.bits.stopBits,
           uart.bits.dataBits, uart.bits.baudRate, uart.bits.interrupt);
    
    printf("\n5. RGB color using bit fields:\n");
    
    struct RGBColor {
        unsigned int blue : 5;   // 5 bits for blue (0-31)
        unsigned int green : 6;  // 6 bits for green (0-63)
        unsigned int red : 5;    // 5 bits for red (0-31)
    };
    
    struct RGBColor color;
    color.red = 31;     // Maximum red
    color.green = 63;   // Maximum green
    color.blue = 31;    // Maximum blue = white
    
    printf("   RGB Color: R=%u, G=%u, B=%u (16-bit color)\n",
           color.red, color.green, color.blue);
    printf("   Size: %zu bytes\n", sizeof(struct RGBColor));
    
    printf("\n6. IP header simulation:\n");
    
    // Simplified IP header (first 32 bits)
    union IPHeader {
        uint32_t raw;
        struct {
            uint32_t version : 4;     // IP version
            uint32_t ihl : 4;         // Internet Header Length
            uint32_t dscp : 6;        // Differentiated Services Code Point
            uint32_t ecn : 2;         // Explicit Congestion Notification
            uint32_t totalLength : 16;// Total length
        } fields;
    };
    
    union IPHeader ip;
    ip.raw = 0x4500003C;  // Typical IP header
    
    printf("   IP Header:\n");
    printf("   Raw: 0x%08X\n", ip.raw);
    printf("   Version: %u, IHL: %u, DSCP: %u, ECN: %u, Length: %u\n",
           ip.fields.version, ip.fields.ihl, ip.fields.dscp,
           ip.fields.ecn, ip.fields.totalLength);
    
    printf("\n7. Date packing using bit fields:\n");
    
    struct PackedDate {
        unsigned int day : 5;     // 5 bits for day (1-31)
        unsigned int month : 4;   // 4 bits for month (1-12)
        unsigned int year : 7;    // 7 bits for year (0-127, offset from 2000)
    };
    
    struct PackedDate date;
    date.day = 15;
    date.month = 6;
    date.year = 23;  // 2023
    
    printf("   Packed Date: %u/%u/20%u\n", date.day, date.month, date.year);
    printf("   Size: %zu bytes (vs 12 bytes for 3 integers)\n", 
           sizeof(struct PackedDate));
    
    printf("\n8. File permissions (Unix-style):\n");
    
    struct FilePermissions {
        unsigned int ownerRead : 1;
        unsigned int ownerWrite : 1;
        unsigned int ownerExecute : 1;
        unsigned int groupRead : 1;
        unsigned int groupWrite : 1;
        unsigned int groupExecute : 1;
        unsigned int othersRead : 1;
        unsigned int othersWrite : 1;
        unsigned int othersExecute : 1;
        unsigned int setuid : 1;
        unsigned int setgid : 1;
        unsigned int sticky : 1;
    };
    
    struct FilePermissions perm;
    perm.ownerRead = perm.ownerWrite = perm.ownerExecute = 1;
    perm.groupRead = perm.groupExecute = 1;
    perm.othersRead = 1;
    
    printf("   File Permissions: ");
    printf("%c%c%c", perm.ownerRead ? 'r' : '-', 
                     perm.ownerWrite ? 'w' : '-', 
                     perm.ownerExecute ? 'x' : '-');
    printf("%c%c%c", perm.groupRead ? 'r' : '-', 
                     perm.groupWrite ? 'w' : '-', 
                     perm.groupExecute ? 'x' : '-');
    printf("%c%c%c", perm.othersRead ? 'r' : '-', 
                     perm.othersWrite ? 'w' : '-', 
                     perm.othersExecute ? 'x' : '-');
    printf("\n");
    
    printf("\n9. Important bit field considerations:\n");
    
    printf("   • Implementation-defined: Order, padding, alignment\n");
    printf("   • No address operator: Cannot use & on bit fields\n");
    printf("   • Portability: May work differently on different compilers\n");
    printf("   • Limited types: Mostly integer types\n");
    printf("   • Range checking: Your responsibility\n");
    printf("   • Performance: May be slower than regular integers\n");
    
    printf("\n10. When to use bit fields:\n");
    
    printf("   • Hardware register access\n");
    printf("   • Network protocol headers\n");
    printf("   • Memory-constrained environments\n");
    printf("   • Packing multiple boolean flags\n");
    printf("   • Storing small-range values efficiently\n");
    printf("   • When exact bit layout is required\n");
    
    return 0;
}
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.