Virtual assistance

C Programming Tutorial

Welcome to AIVista --India's tutorial pages on C Programming

C Programming

Dynamic Memory Allocation in C

Dynamic memory allocation allows programs to allocate memory at runtime from the heap, providing flexibility in memory management.

"Dynamic memory allocation in C is the process of allocating memory at runtime using heap memory with functions like malloc, calloc, realloc, and free."

What is Dynamic Memory Allocation?

Dynamic memory allocation is the process of allocating memory at runtime. Unlike static memory allocation where memory is allocated at compile time, dynamic allocation allows programs to request memory from the heap during execution.

Memory Segments in C

  • Stack: Automatic memory allocation for local variables
  • Heap: Manual memory allocation using dynamic allocation functions
  • Global/Static: Memory for global and static variables
  • Code/Text: Memory for program instructions

malloc() Function

The malloc() function allocates a block of memory of specified size and returns a void pointer to the beginning of the block.

malloc() Syntax

void *malloc(size_t size);

Basic malloc() Usage

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;

    // Allocate memory for one integer
    ptr = (int *)malloc(sizeof(int));

    if (ptr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Use the allocated memory
    *ptr = 42;
    printf("Value: %d\n", *ptr);

    // Free the allocated memory
    free(ptr);

    return 0;
}

Dynamic Array with malloc()

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n, i;
    int *arr;

    printf("Enter the number of elements: ");
    scanf("%d", &n);

    // Allocate memory for n integers
    arr = (int *)malloc(n * sizeof(int));

    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Input elements
    printf("Enter %d elements:\n", n);
    for (i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
    }

    // Display elements
    printf("Elements are:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // Free the allocated memory
    free(arr);

    return 0;
}

calloc() Function

The calloc() function allocates memory for an array of elements, initializes all bytes to zero, and returns a void pointer.

calloc() Syntax

void *calloc(size_t num_elements, size_t element_size);

Basic calloc() Usage

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;
    int n = 5, i;

    // Allocate memory for 5 integers and initialize to zero
    ptr = (int *)calloc(n, sizeof(int));

    if (ptr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Display initialized values (should be 0)
    printf("Initial values:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    // Assign values
    for (i = 0; i < n; i++) {
        ptr[i] = (i + 1) * 10;
    }

    // Display assigned values
    printf("After assignment:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    free(ptr);
    return 0;
}

malloc() vs calloc()

Feature malloc() calloc()
Initialization Garbage values Zero initialized
Parameters One (total size) Two (count, size)
Speed Faster Slower (due to initialization)
Use case Single large block Arrays needing initialization

realloc() Function

The realloc() function changes the size of previously allocated memory block and returns a pointer to the new block.

realloc() Syntax

void *realloc(void *ptr, size_t new_size);

Basic realloc() Usage

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr, *new_ptr;
    int n = 3, i;

    // Initial allocation
    ptr = (int *)malloc(n * sizeof(int));

    if (ptr == NULL) {
        printf("Initial allocation failed!\n");
        return 1;
    }

    // Assign initial values
    for (i = 0; i < n; i++) {
        ptr[i] = i + 1;
    }

    printf("Initial array:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    // Reallocate to larger size
    n = 6;
    new_ptr = (int *)realloc(ptr, n * sizeof(int));

    if (new_ptr == NULL) {
        printf("Reallocation failed!\n");
        free(ptr);
        return 1;
    }

    ptr = new_ptr;

    // Assign values to new elements
    for (i = 3; i < n; i++) {
        ptr[i] = i + 1;
    }

    printf("After reallocation:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    free(ptr);
    return 0;
}

Reallocating to Smaller Size

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;
    int n = 10, i;

    // Allocate memory for 10 integers
    ptr = (int *)malloc(n * sizeof(int));

    // Initialize
    for (i = 0; i < n; i++) {
        ptr[i] = i + 1;
    }

    printf("Original array:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    // Reallocate to smaller size
    n = 5;
    ptr = (int *)realloc(ptr, n * sizeof(int));

    printf("After shrinking:\n");
    for (i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    free(ptr);
    return 0;
}

free() Function

The free() function deallocates the memory previously allocated by malloc(), calloc(), or realloc().

free() Syntax

void free(void *ptr);

Memory Leak Prevention

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr1, *ptr2, *ptr3;

    // Allocate memory
    ptr1 = (int *)malloc(sizeof(int));
    ptr2 = (int *)calloc(5, sizeof(int));
    ptr3 = (int *)malloc(10 * sizeof(int));

    if (ptr1 == NULL || ptr2 == NULL || ptr3 == NULL) {
        printf("Memory allocation failed!\n");
        // Free any successfully allocated memory
        free(ptr1);
        free(ptr2);
        free(ptr3);
        return 1;
    }

    // Use the memory
    *ptr1 = 100;
    ptr2[0] = 200;
    ptr3[0] = 300;

    printf("Values: %d, %d, %d\n", *ptr1, ptr2[0], ptr3[0]);

    // Free all allocated memory
    free(ptr1);
    free(ptr2);
    free(ptr3);

    printf("Memory freed successfully!\n");

    return 0;
}

Dynamic Memory for Structures

Single Structure

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

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

int main() {
    struct Student *student_ptr;

    // Allocate memory for one student structure
    student_ptr = (struct Student *)malloc(sizeof(struct Student));

    if (student_ptr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Input student details
    printf("Enter roll number: ");
    scanf("%d", &student_ptr->roll_number);
    printf("Enter name: ");
    scanf("%s", student_ptr->name);
    printf("Enter marks: ");
    scanf("%f", &student_ptr->marks);

    // Display student details
    printf("\nStudent Details:\n");
    printf("Roll Number: %d\n", student_ptr->roll_number);
    printf("Name: %s\n", student_ptr->name);
    printf("Marks: %.2f\n", student_ptr->marks);

    // Free allocated memory
    free(student_ptr);

    return 0;
}

Array of Structures

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

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

int main() {
    struct Employee *employees;
    int n, i;

    printf("Enter number of employees: ");
    scanf("%d", &n);

    // Allocate memory for array of structures
    employees = (struct Employee *)malloc(n * sizeof(struct Employee));

    if (employees == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Input employee details
    for (i = 0; i < n; i++) {
        printf("\nEmployee %d:\n", i + 1);
        printf("Enter ID: ");
        scanf("%d", &employees[i].id);
        printf("Enter name: ");
        scanf("%s", employees[i].name);
        printf("Enter salary: ");
        scanf("%f", &employees[i].salary);
    }

    // Display employee details
    printf("\nEmployee Details:\n");
    printf("ID\tName\t\tSalary\n");
    printf("---\t----\t\t------\n");

    for (i = 0; i < n; i++) {
        printf("%d\t%s\t\t%.2f\n",
               employees[i].id,
               employees[i].name,
               employees[i].salary);
    }

    // Free allocated memory
    free(employees);

    return 0;
}

Dynamic 2D Arrays

Array of Pointers Method

#include <stdio.h>
#include <stdlib.h>

int main() {
    int **matrix;
    int rows = 3, cols = 4, i, j;

    // Allocate memory for array of pointers (rows)
    matrix = (int **)malloc(rows * sizeof(int *));

    if (matrix == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Allocate memory for each row
    for (i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
        if (matrix[i] == NULL) {
            printf("Memory allocation failed for row %d!\n", i);
            // Free previously allocated memory
            for (j = 0; j < i; j++) {
                free(matrix[j]);
            }
            free(matrix);
            return 1;
        }
    }

    // Initialize and display matrix
    printf("Matrix elements:\n");
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            matrix[i][j] = (i + 1) * (j + 1);
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    // Free allocated memory
    for (i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

Single Block Allocation Method

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *matrix;
    int rows = 3, cols = 4, i, j;

    // Allocate single block of memory
    matrix = (int *)malloc(rows * cols * sizeof(int));

    if (matrix == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Initialize matrix using pointer arithmetic
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            *(matrix + i * cols + j) = (i + 1) * (j + 1);
        }
    }

    // Display matrix
    printf("Matrix elements:\n");
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            printf("%d ", *(matrix + i * cols + j));
        }
        printf("\n");
    }

    // Free allocated memory
    free(matrix);

    return 0;
}

Memory Management Best Practices

1. Always Check Allocation Success

// Good practice
int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
    printf("Memory allocation failed!\n");
    exit(1);
}

2. Always Free Allocated Memory

// Good practice
int *ptr = (int *)malloc(sizeof(int));
// Use ptr
free(ptr);
ptr = NULL; // Optional: prevent dangling pointer

3. Avoid Memory Leaks

// Bad practice - memory leak
void bad_function() {
    int *ptr = (int *)malloc(sizeof(int));
    // Function ends without freeing ptr
}

// Good practice
void good_function() {
    int *ptr = (int *)malloc(sizeof(int));
    if (ptr != NULL) {
        // Use ptr
        free(ptr);
    }
}

4. Handle Reallocation Carefully

// Good practice
int *ptr = (int *)malloc(initial_size * sizeof(int));
if (ptr != NULL) {
    // Use ptr
    int *new_ptr = (int *)realloc(ptr, new_size * sizeof(int));
    if (new_ptr != NULL) {
        ptr = new_ptr;
        // Continue using ptr
    } else {
        // Handle reallocation failure
        free(ptr);
        return ERROR;
    }
    free(ptr);
}

Common Memory Errors

1. Dangling Pointers

// Problematic code
int *ptr = (int *)malloc(sizeof(int));
free(ptr);
// ptr is now a dangling pointer
// *ptr = 10; // Undefined behavior

2. Double Free

// Problematic code
int *ptr = (int *)malloc(sizeof(int));
free(ptr);
free(ptr); // Double free - undefined behavior

3. Memory Corruption

// Problematic code
int *arr = (int *)malloc(5 * sizeof(int));
// Accessing beyond allocated memory
arr[10] = 100; // Memory corruption

Practical Examples

1. Dynamic Stack Implementation

#include <stdio.h>
#include <stdlib.h>

struct Stack {
    int *arr;
    int top;
    int capacity;
};

struct Stack* createStack(int capacity) {
    struct Stack *stack = (struct Stack*)malloc(sizeof(struct Stack));
    if (stack == NULL) return NULL;

    stack->capacity = capacity;
    stack->top = -1;
    stack->arr = (int*)malloc(stack->capacity * sizeof(int));

    if (stack->arr == NULL) {
        free(stack);
        return NULL;
    }

    return stack;
}

int isFull(struct Stack* stack) {
    return stack->top == stack->capacity - 1;
}

int isEmpty(struct Stack* stack) {
    return stack->top == -1;
}

void push(struct Stack* stack, int item) {
    if (isFull(stack)) {
        // Double the capacity
        stack->capacity *= 2;
        stack->arr = (int*)realloc(stack->arr, stack->capacity * sizeof(int));
        if (stack->arr == NULL) {
            printf("Memory reallocation failed!\n");
            return;
        }
    }
    stack->arr[++stack->top] = item;
    printf("%d pushed to stack\n", item);
}

int pop(struct Stack* stack) {
    if (isEmpty(stack)) {
        printf("Stack underflow!\n");
        return -1;
    }
    return stack->arr[stack->top--];
}

void freeStack(struct Stack* stack) {
    free(stack->arr);
    free(stack);
}

int main() {
    struct Stack* stack = createStack(2);

    push(stack, 10);
    push(stack, 20);
    push(stack, 30); // This will trigger reallocation

    printf("%d popped from stack\n", pop(stack));
    printf("%d popped from stack\n", pop(stack));

    freeStack(stack);
    return 0;
}

2. Dynamic String Manipulation

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

char* concatenateStrings(const char* str1, const char* str2) {
    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);
    size_t total_len = len1 + len2 + 1; // +1 for null terminator

    char* result = (char*)malloc(total_len * sizeof(char));

    if (result == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }

    strcpy(result, str1);
    strcat(result, str2);

    return result;
}

char* reverseString(const char* str) {
    size_t len = strlen(str);
    char* reversed = (char*)malloc((len + 1) * sizeof(char));

    if (reversed == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }

    for (size_t i = 0; i < len; i++) {
        reversed[i] = str[len - 1 - i];
    }
    reversed[len] = '\0';

    return reversed;
}

int main() {
    char str1[] = "Hello";
    char str2[] = " World";

    // Concatenate strings
    char* concatenated = concatenateStrings(str1, str2);
    if (concatenated != NULL) {
        printf("Concatenated: %s\n", concatenated);
        free(concatenated);
    }

    // Reverse string
    char* reversed = reverseString(str1);
    if (reversed != NULL) {
        printf("Reversed: %s\n", reversed);
        free(reversed);
    }

    return 0;
}

Conclusion

Dynamic memory allocation is a powerful feature in C that allows flexible memory management. Understanding malloc, calloc, realloc, and free is essential for writing efficient C programs. Always remember to free allocated memory to prevent memory leaks and handle allocation failures gracefully! 🚀