Docs

Malloc and Free

malloc() and free() - Memory Allocation and Deallocation

Table of Contents

  1. •Overview
  2. •Understanding malloc()
  3. •malloc() Syntax and Usage
  4. •The sizeof Operator
  5. •Type Casting with malloc()
  6. •Understanding free()
  7. •Memory Allocation Internals
  8. •Common Patterns
  9. •Error Handling
  10. •Common Mistakes
  11. •Debugging Memory Issues
  12. •Best Practices
  13. •Summary

Overview

malloc() (memory allocation) and free() are the fundamental functions for dynamic memory management in C. They allow programs to request and release memory at runtime, providing flexibility that static allocation cannot offer.

Learning Objectives

  • •Master the syntax and usage of malloc()
  • •Understand proper memory deallocation with free()
  • •Learn about memory allocation internals
  • •Identify and avoid common memory-related bugs
  • •Apply best practices for memory management

Understanding malloc()

What is malloc()?

malloc() stands for memory allocation. It is a function that:

  1. •Requests a block of memory from the heap
  2. •Returns a pointer to the beginning of that block
  3. •Does NOT initialize the memory (contents are indeterminate)

Function Declaration

#include <stdlib.h>

void *malloc(size_t size);

Parameters

ParameterTypeDescription
sizesize_tNumber of bytes to allocate

Return Value

ConditionReturn Value
SuccessPointer to allocated memory (as void*)
FailureNULL (typically when out of memory)

Key Characteristics

  1. •Uninitialized Memory: Contains garbage values
  2. •Heap Allocation: Memory comes from the heap
  3. •Alignment: Memory is suitably aligned for any data type
  4. •Persistence: Memory remains until explicitly freed
  5. •Generic Pointer: Returns void* for type flexibility

malloc() Syntax and Usage

Basic Syntax

pointer = (type*)malloc(number_of_bytes);

Common Usage Patterns

Allocating a Single Variable

// Allocate one integer
int *p = (int*)malloc(sizeof(int));
if (p == NULL) {
    // Handle allocation failure
}
*p = 42;

// Allocate one double
double *d = (double*)malloc(sizeof(double));
if (d != NULL) {
    *d = 3.14159;
}

Allocating an Array

// Allocate array of 10 integers
int *arr = (int*)malloc(10 * sizeof(int));

// Allocate array of n doubles (n determined at runtime)
int n = getUserInput();
double *data = (double*)malloc(n * sizeof(double));

Allocating a String

// Allocate string buffer
char *str = (char*)malloc(100 * sizeof(char));

// Allocate exact size for copy
const char *original = "Hello, World!";
char *copy = (char*)malloc(strlen(original) + 1);  // +1 for '\0'
strcpy(copy, original);

Allocating a Structure

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

Employee *emp = (Employee*)malloc(sizeof(Employee));
if (emp != NULL) {
    emp->id = 101;
    strcpy(emp->name, "John Doe");
    emp->salary = 50000.0;
}

The sizeof Operator

Why Use sizeof?

The sizeof operator ensures:

  1. •Portability: Type sizes vary across systems
  2. •Correctness: Exact number of bytes for the type
  3. •Maintainability: Changes automatically if type changes

Type Sizes (Typical)

Type32-bit System64-bit System
char1 byte1 byte
short2 bytes2 bytes
int4 bytes4 bytes
long4 bytes8 bytes
float4 bytes4 bytes
double8 bytes8 bytes
pointer4 bytes8 bytes

Two Forms of sizeof

// Form 1: sizeof with type
int *p1 = malloc(sizeof(int));
int *arr1 = malloc(10 * sizeof(int));

// Form 2: sizeof with expression (PREFERRED)
int *p2 = malloc(sizeof(*p2));
int *arr2 = malloc(10 * sizeof(*arr2));

Why sizeof(*pointer) is Better

// If you change the type, only one place to update:
double *data = malloc(n * sizeof(*data));
// Type appears only in declaration, sizeof adjusts automatically

// Compare to:
double *data = malloc(n * sizeof(double));
// Type appears twice - easy to mismatch

sizeof for Arrays vs Pointers

int staticArray[10];
int *dynamicArray = malloc(10 * sizeof(int));

sizeof(staticArray);   // Returns 40 (10 * 4 bytes)
sizeof(dynamicArray);  // Returns 4 or 8 (just the pointer!)

Important: sizeof on a pointer returns the pointer size, not the allocated memory size. There's no way to determine allocation size from the pointer alone.


Type Casting with malloc()

In C

// Without cast (valid in C, implicit conversion from void*)
int *p1 = malloc(sizeof(int));

// With cast (explicit, shows intent)
int *p2 = (int*)malloc(sizeof(int));

In C++

// Cast is REQUIRED in C++
int *p = (int*)malloc(sizeof(int));
// Or prefer: int *p = new int;

Best Practice

For C code, the cast is optional. Many style guides recommend:

  • •No cast: Relies on implicit conversion, cleaner
  • •With cast: More explicit, compatible with C++
// Recommended pattern (no cast, sizeof with variable)
int *p = malloc(sizeof *p);

Understanding free()

What is free()?

free() returns dynamically allocated memory to the heap, making it available for future allocations.

Function Declaration

#include <stdlib.h>

void free(void *ptr);

Parameters

ParameterTypeDescription
ptrvoid*Pointer to memory previously allocated by malloc/calloc/realloc

Return Value

free() returns nothing (void).

Key Behaviors

int *p = malloc(sizeof(int));
*p = 42;

free(p);  // Memory is now deallocated
// p still holds the address, but memory is invalid

p = NULL;  // Good practice: prevents accidental reuse

What free() Does

  1. •Marks memory as available for future allocations
  2. •Does NOT clear the memory contents
  3. •Does NOT change the pointer value
  4. •Updates internal heap management structures

Special Cases

// Passing NULL is safe (no operation)
free(NULL);  // Does nothing, no error

// Passing unallocated memory is UNDEFINED BEHAVIOR
int x = 10;
free(&x);    // WRONG! x is on stack

// Double free is UNDEFINED BEHAVIOR
int *p = malloc(100);
free(p);
free(p);     // WRONG! Already freed

Memory Allocation Internals

How the Heap Works

Heap Memory
+---------+------------------+---------+------------------+----------+
| Header1 | Allocated Block1 | Header2 | Allocated Block2 | Free     |
| (meta)  | (your data)      | (meta)  | (your data)      | Space    |
+---------+------------------+---------+------------------+----------+
          ^                            ^
          |                            |
    malloc returns            malloc returns
    this address              this address

Metadata (Header) Contains

  • •Size of the block
  • •Allocation status (free or in use)
  • •Pointers to adjacent blocks
  • •Magic numbers for debugging

Memory Layout Example

malloc(100) returns:

+--------+----------------------------------+
| Header |         100 bytes                |
| ~16 B  |         (your data)              |
+--------+----------------------------------+
         ^
         Pointer returned to user

Fragmentation

Over time, repeated malloc/free can cause fragmentation:

Before many allocations:
[====================Free Space====================]

After many alloc/free cycles:
[Used][Free][Used][Free][Used][Free][Used][Free][Used]
      ^^^^       ^^^^       ^^^^       ^^^^
      These small free blocks may be too small for new allocations

Allocation Strategies

Memory allocators use various strategies:

  1. •First Fit: Use first block large enough
  2. •Best Fit: Use smallest block that fits
  3. •Worst Fit: Use largest block
  4. •Buddy System: Power-of-2 block sizes

Common Patterns

Pattern 1: Allocate-Use-Free

void processData(int n) {
    // Allocate
    int *data = malloc(n * sizeof(*data));
    if (data == NULL) {
        fprintf(stderr, "Allocation failed\n");
        return;
    }

    // Use
    for (int i = 0; i < n; i++) {
        data[i] = i * 2;
    }

    // Free
    free(data);
}

Pattern 2: Factory Function

// Function that creates and returns allocated memory
char* createGreeting(const char *name) {
    // Calculate size: "Hello, " (7) + name + "!" (1) + null (1)
    size_t size = 7 + strlen(name) + 2;

    char *greeting = malloc(size);
    if (greeting != NULL) {
        sprintf(greeting, "Hello, %s!", name);
    }
    return greeting;  // Caller must free
}

// Usage
char *msg = createGreeting("Alice");
printf("%s\n", msg);
free(msg);  // Don't forget!

Pattern 3: Array of Pointers

// Array of strings
char **createStringArray(int count) {
    char **arr = malloc(count * sizeof(char*));
    if (arr == NULL) return NULL;

    for (int i = 0; i < count; i++) {
        arr[i] = malloc(100);  // Each string up to 100 chars
        if (arr[i] == NULL) {
            // Cleanup on failure
            while (--i >= 0) free(arr[i]);
            free(arr);
            return NULL;
        }
    }
    return arr;
}

// Free in reverse
void freeStringArray(char **arr, int count) {
    if (arr != NULL) {
        for (int i = 0; i < count; i++) {
            free(arr[i]);
        }
        free(arr);
    }
}

Pattern 4: Structure with Pointer Members

typedef struct {
    char *name;
    int *scores;
    int scoreCount;
} Student;

Student* createStudent(const char *name, int numScores) {
    Student *s = malloc(sizeof(Student));
    if (s == NULL) return NULL;

    s->name = malloc(strlen(name) + 1);
    if (s->name == NULL) {
        free(s);
        return NULL;
    }
    strcpy(s->name, name);

    s->scores = malloc(numScores * sizeof(int));
    if (s->scores == NULL) {
        free(s->name);
        free(s);
        return NULL;
    }

    s->scoreCount = numScores;
    return s;
}

void freeStudent(Student *s) {
    if (s != NULL) {
        free(s->scores);
        free(s->name);
        free(s);
    }
}

Error Handling

Why malloc() Can Fail

  1. •Insufficient memory: System is out of RAM
  2. •Fragmentation: No contiguous block large enough
  3. •Process limits: Exceeded maximum heap size
  4. •Integer overflow: Requested size calculation overflowed

Always Check Return Value

// WRONG: No error check
int *p = malloc(SIZE);
*p = 10;  // Crash if malloc failed!

// CORRECT: Check for NULL
int *p = malloc(SIZE);
if (p == NULL) {
    fprintf(stderr, "Error: Memory allocation failed\n");
    exit(EXIT_FAILURE);
}
*p = 10;

Error Handling Strategies

Strategy 1: Exit on Failure

void *safe_malloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr == NULL) {
        fprintf(stderr, "Fatal error: Out of memory\n");
        exit(EXIT_FAILURE);
    }
    return ptr;
}

Strategy 2: Return Error Code

int allocateBuffer(char **buffer, size_t size) {
    *buffer = malloc(size);
    if (*buffer == NULL) {
        return -1;  // Error
    }
    return 0;  // Success
}

Strategy 3: Graceful Degradation

// Try smaller allocation if large one fails
int *data = malloc(LARGE_SIZE);
if (data == NULL) {
    data = malloc(SMALL_SIZE);
    if (data == NULL) {
        // Handle complete failure
    }
    // Work with smaller buffer
}

Common Mistakes

Mistake 1: Memory Leaks

// WRONG: Lost pointer causes leak
void leaky(void) {
    int *p = malloc(100);
    // No free() - memory leaked when function returns
}

// WRONG: Overwriting pointer
int *p = malloc(100);
p = malloc(200);  // First allocation leaked!

// CORRECT:
int *p = malloc(100);
free(p);
p = malloc(200);

Mistake 2: Use After Free

int *p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);  // WRONG: Using freed memory!

// CORRECT:
free(p);
p = NULL;  // Prevents accidental use

Mistake 3: Double Free

int *p = malloc(100);
free(p);
free(p);  // WRONG: Double free!

// CORRECT:
free(p);
p = NULL;
free(p);  // Safe: free(NULL) does nothing

Mistake 4: Freeing Non-Heap Memory

int x = 10;
free(&x);  // WRONG: x is on stack!

char str[] = "Hello";
free(str);  // WRONG: str is on stack!

char *ptr = "Constant";
free(ptr);  // WRONG: String literal in read-only memory!

Mistake 5: Buffer Overflow

char *str = malloc(5);
strcpy(str, "Hello");  // WRONG: "Hello\0" is 6 bytes!

// CORRECT:
char *str = malloc(strlen("Hello") + 1);
strcpy(str, "Hello");

Mistake 6: Wrong Size Calculation

// WRONG: sizeof(int*) instead of sizeof(int)
int *arr = malloc(10 * sizeof(int*));  // Allocates for pointers, not ints!

// CORRECT:
int *arr = malloc(10 * sizeof(int));
// Or better:
int *arr = malloc(10 * sizeof(*arr));

Mistake 7: Integer Overflow

// If n is large, n * sizeof(int) might overflow
size_t n = get_huge_number();
int *arr = malloc(n * sizeof(int));  // Might allocate less than expected!

// CORRECT: Check for overflow
if (n > SIZE_MAX / sizeof(int)) {
    // Handle overflow
}
int *arr = malloc(n * sizeof(int));

Debugging Memory Issues

Tool 1: Valgrind (Linux)

# Compile with debug symbols
gcc -g program.c -o program

# Run with Valgrind
valgrind --leak-check=full ./program

Valgrind detects:

  • •Memory leaks
  • •Use of uninitialized memory
  • •Invalid reads/writes
  • •Double frees
  • •Mismatched free (malloc with delete, etc.)

Tool 2: AddressSanitizer

# Compile with sanitizer
gcc -fsanitize=address -g program.c -o program

# Run normally
./program

AddressSanitizer detects:

  • •Buffer overflows
  • •Use after free
  • •Double free
  • •Memory leaks (with additional flags)

Tool 3: Debug malloc Libraries

Many systems provide debug versions:

  • •Electric Fence
  • •DUMA
  • •tcmalloc (with heap checker)

Manual Debugging Techniques

// Technique 1: Wrapper functions with logging
void *debug_malloc(size_t size, const char *file, int line) {
    void *ptr = malloc(size);
    printf("MALLOC: %zu bytes at %p (%s:%d)\n", size, ptr, file, line);
    return ptr;
}

void debug_free(void *ptr, const char *file, int line) {
    printf("FREE: %p (%s:%d)\n", ptr, file, line);
    free(ptr);
}

#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr, __FILE__, __LINE__)
// Technique 2: Memory fill patterns
void *debug_malloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr) memset(ptr, 0xAA, size);  // Fill with pattern
    return ptr;
}

void debug_free(void *ptr, size_t size) {
    if (ptr) memset(ptr, 0xDD, size);  // Fill before free
    free(ptr);
}

Best Practices

1. Always Check Return Value

int *p = malloc(size);
if (p == NULL) {
    // Handle error
    return ERROR_CODE;
}

2. Use sizeof(*pointer)

// Good: type only in one place
double *data = malloc(n * sizeof(*data));

// Less good: type repeated
double *data = malloc(n * sizeof(double));

3. Set Pointer to NULL After Free

free(ptr);
ptr = NULL;

4. Free in Reverse Order of Allocation

a = malloc(...);
b = malloc(...);
c = malloc(...);

// Free in reverse
free(c);
free(b);
free(a);

5. Document Ownership

/**
 * Creates a new string copy.
 * @return Newly allocated string. Caller must free().
 */
char *duplicateString(const char *s);

/**
 * Frees a Person structure and all its members.
 */
void destroyPerson(Person *p);

6. Initialize Memory After Allocation

int *arr = malloc(n * sizeof(*arr));
if (arr != NULL) {
    memset(arr, 0, n * sizeof(*arr));  // Or use calloc
}

7. Match Allocation and Deallocation

AllocationDeallocation
malloc()free()
calloc()free()
realloc()free()
new (C++)delete
new[] (C++)delete[]

8. Use Consistent Error Handling

// One approach: wrapper function
void *safe_malloc(size_t size) {
    void *p = malloc(size);
    if (p == NULL && size != 0) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    return p;
}

Summary

Key Points

  1. •malloc(size) allocates size bytes and returns a pointer
  2. •free(ptr) deallocates memory previously allocated
  3. •Always check if malloc returns NULL
  4. •Always free allocated memory when done
  5. •Set pointers to NULL after freeing
  6. •Use sizeof(*ptr) for reliable size calculation
  7. •Never use memory after freeing
  8. •Never free the same memory twice
  9. •Never free non-heap memory

Quick Reference

#include <stdlib.h>

// Allocate
int *p = malloc(sizeof(*p));           // Single value
int *arr = malloc(n * sizeof(*arr));   // Array

// Check
if (p == NULL) { /* error */ }

// Use
*p = 42;
arr[0] = 10;

// Deallocate
free(p);
p = NULL;
free(arr);
arr = NULL;

Memory Management Checklist

  • • Include <stdlib.h>
  • • Use sizeof for size calculation
  • • Check return value for NULL
  • • Initialize memory if needed
  • • Free all allocated memory
  • • Set pointers to NULL after free
  • • No use-after-free
  • • No double-free
  • • No freeing non-heap memory
  • • Test with Valgrind or AddressSanitizer

Quick Reference Card

// === MALLOC ===
void *malloc(size_t size);
// Returns: pointer to allocated memory, or NULL on failure
// Memory is NOT initialized

// === FREE ===
void free(void *ptr);
// Returns: nothing
// Passing NULL is safe

// === PATTERNS ===
// Single value
Type *p = malloc(sizeof(*p));

// Array
Type *arr = malloc(count * sizeof(*arr));

// String copy
char *copy = malloc(strlen(orig) + 1);

// Structure
Struct *s = malloc(sizeof(*s));

// === SAFETY ===
if (ptr == NULL) { /* handle error */ }
free(ptr);
ptr = NULL;
Malloc And Free - C Programming Tutorial | DeepML