C 4

Phase 4 — Pointers & Memory ⭐

This is the most important low-level phase in C.

The central idea is:

A normal variable stores a value. A pointer stores an address where a value lives.

Once this becomes natural, arrays, strings, dynamic memory, structures, linked lists, callbacks, and much of systems programming become significantly easier.


Module 1 — Understanding Addresses & Pointers

Chapter 1 — Memory Addresses

Question

Given below is a program that creates two integer variables and prints their values and addresses.

What should the output look like?

#include <stdio.h>

int main(void)
{
    int age = 35;
    int score = 90;

    // Print the values stored in the variables.
    printf("age   = %d\n", age);
    printf("score = %d\n", score);

    // Print the memory addresses of the variables.
    printf("&age   = %p\n", (void *)&age);
    printf("&score = %p\n", (void *)&score);

    return 0;
}

Answer

age   = 35
score = 90
&age   = 0x........
&score = 0x........

The exact addresses cannot be predicted.

Step-by-step explanation

  1. age stores 35.

  2. score stores 90.

  3. &age means "address of age."

  4. &score means "address of score."

  5. %p is used to print an address.

  6. The (void *) cast is the conventional form for passing a pointer to printf("%p").

Think of memory like houses:

Memory

Address        Value
1000           35       ← age
1004           90       ← score

The address is where the value lives.

How to read the important code

&age

Read naturally as:

"the address of age"

%p

Read as:

"print a pointer/address"

Key takeaway

&variable gives you the variable's memory address.


Chapter 2 — What Is a Pointer?

Question

What will this print?

#include <stdio.h>

int main(void)
{
    int age = 35;

    // p stores the address of age.
    int *p = &age;

    printf("%d\n", age);
    printf("%d\n", *p);

    return 0;
}

Answer

35
35

Step-by-step explanation

This line:

int *p = &age;

means:

Create a pointer named p that can point to an int, and store the address of age inside it.

Imagine:

age
┌───────┐
│  35   │
└───────┘
   ↑
   │
   p

p contains the address.

Then:

*p

means:

Go to the address stored inside p and access the value there.

Therefore:

age

and

*p

both produce 35.

Key takeaway

p is the address; *p accesses the value at that address.


Chapter 3 — Dereferencing a Pointer

Question

What will the final value of x be?

#include <stdio.h>

int main(void)
{
    int x = 10;

    // p points to x.
    int *p = &x;

    // Change x through its pointer.
    *p = 50;

    printf("%d\n", x);

    return 0;
}

Answer

50

Step-by-step explanation

Initially:

x = 10

Then:

p = &x;

Now p points to x.

Then:

*p = 50;

means:

Go to the memory location represented by p and put 50 there.

So:

Before:

x → 10

After:

x → 50

Key takeaway

Dereferencing lets a pointer modify the object it points to.


Chapter 4 — Pointer Types

Question

What does this program print?

#include <stdio.h>

int main(void)
{
    int x = 100;
    double y = 3.14;

    // Each pointer is designed for a particular type.
    int *ip = &x;
    double *dp = &y;

    printf("%d\n", *ip);
    printf("%.2f\n", *dp);

    return 0;
}

Answer

100
3.14

Step-by-step explanation

These are different pointer types:

int *ip;
double *dp;

ip is an int *.

dp is a double *.

The type matters because C needs to know how to interpret the memory and how pointer arithmetic should behave.

For example:

int *p;

means:

p points to an int.

Key takeaway

A pointer has a type describing the kind of object it points to.


Module 2 — Pointers and Arrays

Chapter 5 — Arrays and Addresses

Question

What will this print?

#include <stdio.h>

int main(void)
{
    int numbers[] = {10, 20, 30};

    // The array name represents the address of its first element
    // in this expression.
    int *p = numbers;

    printf("%d\n", *p);
    printf("%d\n", *(p + 1));
    printf("%d\n", *(p + 2));

    return 0;
}

Answer

10
20
30

Step-by-step explanation

The array is conceptually:

numbers

┌────┬────┬────┐
│ 10 │ 20 │ 30 │
└────┴────┴────┘
  ↑
  p

p points to the first element.

So:

*p

10

*(p + 1)

20

*(p + 2)

30

Important:

p + 1 does not necessarily mean one byte forward.

For an int *, it means:

Move forward by one int.

Key takeaway

Pointer arithmetic moves according to the size of the pointed-to type.


Chapter 6 — Pointer Arithmetic

Question

Assume sizeof(int) == 4.

What values are printed?

#include <stdio.h>

int main(void)
{
    int numbers[] = {10, 20, 30};

    int *p = numbers;

    printf("%d\n", *p);

    p++;
    printf("%d\n", *p);

    p++;
    printf("%d\n", *p);

    return 0;
}

Answer

10
20
30

Step-by-step explanation

Initially:

p → numbers[0]

After:

p++;
p → numbers[1]

After another:

p++;
p → numbers[2]

If an int occupies 4 bytes:

1000 → 10
1004 → 20
1008 → 30

p++ moves from 1000 to 1004, not 1001.

Key takeaway

p + n moves by n elements, not n bytes.


Chapter 7 — Arrays and Pointers Are Not the Same Thing

Question

Which statements are true?

#include <stdio.h>

int main(void)
{
    int a[] = {10, 20, 30};

    int *p = a;

    printf("%zu\n", sizeof(a));
    printf("%zu\n", sizeof(p));

    return 0;
}

Answer

sizeof(a) gives the size of the entire array.

sizeof(p) gives the size of the pointer itself.

For example, on a common 64-bit system:

sizeof(a) → 12
sizeof(p) → 8

assuming sizeof(int) == 4.

Step-by-step explanation

An array:

int a[3];

actually contains three int objects.

A pointer:

int *p;

contains an address.

Therefore:

array → actual elements
pointer → address

They are closely related, but they are not the same type.

Key takeaway

An array is storage for elements; a pointer is an object containing an address.


Module 3 — Pointers and Functions

Chapter 8 — Passing a Pointer to a Function

Question

What is printed?

#include <stdio.h>

void change(int *p)
{
    // Modify the caller's variable.
    *p = 99;
}

int main(void)
{
    int x = 10;

    change(&x);

    printf("%d\n", x);

    return 0;
}

Answer

99

Step-by-step explanation

The call:

change(&x);

passes the address of x.

Inside:

void change(int *p)

p receives that address.

Then:

*p = 99;

changes the original x.

This is one of the most important uses of pointers.

Java comparison

Java also passes arguments by value, but object references make the behavior look different.

C gives you explicit control over the address.

Key takeaway

Passing an address allows a function to modify the caller's object.


Chapter 9 — C Is Pass-by-Value

Question

Will x become 100?

#include <stdio.h>

void change(int x)
{
    // This changes only the local copy.
    x = 100;
}

int main(void)
{
    int x = 10;

    change(x);

    printf("%d\n", x);

    return 0;
}

Answer

10

Step-by-step explanation

C passes the argument by value.

So:

change(x);

creates a copy.

Conceptually:

main's x       → 10

function's x   → 10
                   ↓
                  100

The function changes its own copy.

To modify the original, pass its address:

change(&x);

Key takeaway

C does not have true pass-by-reference; pointers are used to achieve reference-like behavior.


Module 4 — NULL, Invalid & Dangerous Pointers

Chapter 10 — NULL Pointers

Question

What is safe about this code?

#include <stdio.h>

int main(void)
{
    int *p = NULL;

    if (p != NULL) {
        printf("%d\n", *p);
    }

    return 0;
}

Answer

The program does not dereference p.

Step-by-step explanation

NULL means:

This pointer currently does not point to a valid object.

This is dangerous:

*p

when p == NULL.

Instead:

if (p != NULL)

checks first.

Key takeaway

Never dereference a null pointer.


Chapter 11 — Wild Pointers

Question

Is this safe?

#include <stdio.h>

int main(void)
{
    int *p;

    *p = 10;

    return 0;
}

Answer

No.

p is uninitialized.

It contains an indeterminate value and does not point to a valid int object.

Dereferencing it produces undefined behavior.

Key takeaway

An uninitialized pointer is dangerous; initialize pointers deliberately.


Chapter 12 — Dangling Pointers

Question

What is wrong here?

#include <stdio.h>

int *get_pointer(void)
{
    int x = 10;

    // x stops existing when this function returns.
    return &x;
}

int main(void)
{
    int *p = get_pointer();

    printf("%d\n", *p);

    return 0;
}

Answer

p becomes a dangling pointer.

Step-by-step explanation

Inside get_pointer():

int x = 10;

x is a local variable.

When the function returns:

x's lifetime ends

But the returned address still exists inside p.

So:

p → dead object

Dereferencing it is undefined behavior.

Key takeaway

Never return a pointer to a local automatic variable.


Module 5 — Pointers to Structures

Chapter 13 — -> Operator

Question

What is printed?

#include <stdio.h>

struct Person {
    int age;
};

int main(void)
{
    struct Person person = {35};

    // Point to the structure.
    struct Person *p = &person;

    // Access the member through the pointer.
    p->age = 36;

    printf("%d\n", person.age);

    return 0;
}

Answer

36

Step-by-step explanation

Without a pointer:

person.age

With a pointer:

p->age

These are equivalent:

p->age

and:

(*p).age

The arrow is simply the convenient syntax.

Key takeaway

p->member means "access this structure member through pointer p."


Module 6 — Pointer to Pointer

Chapter 14 — **

Question

What is printed?

#include <stdio.h>

int main(void)
{
    int x = 10;

    // p points to x.
    int *p = &x;

    // pp points to p.
    int **pp = &p;

    **pp = 50;

    printf("%d\n", x);

    return 0;
}

Answer

50

Step-by-step explanation

We have three levels:

x
↑
p
↑
pp

More precisely:

pp
 ↓
 p
 ↓
 x

Therefore:

*pp

gives p.

And:

**pp

gives x.

So:

**pp = 50;

changes x.

Key takeaway

int ** is a pointer to an int *.


Module 7 — void *, Arrays of Pointers & Function Pointers

Chapter 15 — void *

Question

What happens here?

#include <stdio.h>

int main(void)
{
    int x = 42;

    // Generic object pointer.
    void *p = &x;

    // Convert back to int pointer before dereferencing.
    printf("%d\n", *(int *)p);

    return 0;
}

Answer

42

Step-by-step explanation

void * can hold the address of an object of essentially any object type.

But C does not know what type the pointed-to object is.

Therefore this is not generally valid:

*p

Instead, convert it to the appropriate pointer type:

*(int *)p

Key takeaway

void * is a generic object pointer, but you need the correct type before dereferencing it.


Chapter 16 — Arrays of Pointers

Question

What is printed?

#include <stdio.h>

int main(void)
{
    int a = 10;
    int b = 20;

    // Each element stores an address.
    int *ptrs[] = {&a, &b};

    printf("%d\n", *ptrs[0]);
    printf("%d\n", *ptrs[1]);

    return 0;
}

Answer

10
20

Step-by-step explanation

This:

int *ptrs[2];

means:

An array containing two pointers to int.

Conceptually:

ptrs[0] → a → 10
ptrs[1] → b → 20

Key takeaway

int *p[3] means "array of 3 pointers to int."


Chapter 17 — Function Pointers

Question

What is printed?

#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int main(void)
{
    // fp points to a function taking two ints and returning int.
    int (*fp)(int, int) = add;

    printf("%d\n", fp(10, 20));

    return 0;
}

Answer

30

Step-by-step explanation

Functions also have addresses.

This declaration:

int (*fp)(int, int);

means:

fp is a pointer to a function that takes two int arguments and returns an int.

Then:

fp = add;

makes it point to add.

Therefore:

fp(10, 20)

calls add.

Function pointers become important for:

  • callbacks

  • event systems

  • sorting

  • APIs

  • state machines

  • operating-system interfaces

Key takeaway

A function pointer stores the address of a function and can be used to call it.


Module 8 — Dynamic Memory ⭐

Now we move from pointers to heap memory.

The key distinction:

Stack
─────
automatic/local objects

Heap
────
dynamically allocated memory

Chapter 18 — malloc

Question

What does this program print?

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

int main(void)
{
    // Allocate space for one int on the heap.
    int *p = malloc(sizeof *p);

    // Always check whether allocation succeeded.
    if (p == NULL) {
        return 1;
    }

    *p = 42;

    printf("%d\n", *p);

    // Release the allocated memory.
    free(p);

    return 0;
}

Answer

42

Step-by-step explanation

malloc(sizeof *p)

requests enough heap memory for one int.

If successful:

p
 ↓
┌──────┐
│  42  │
└──────┘
 heap

Then:

free(p);

returns that memory to the allocator.

Important pattern

int *p = malloc(sizeof *p);

if (p == NULL) {
    // allocation failed
}

Then eventually:

free(p);

Key takeaway

malloc allocates heap memory; free releases it.


Chapter 19 — calloc

Question

What will this print?

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

int main(void)
{
    // Allocate space for 3 ints and initialize all bytes to zero.
    int *numbers = calloc(3, sizeof *numbers);

    if (numbers == NULL) {
        return 1;
    }

    printf("%d %d %d\n",
           numbers[0],
           numbers[1],
           numbers[2]);

    free(numbers);

    return 0;
}

Answer

0 0 0

Step-by-step explanation

calloc allocates:

3 × sizeof(int)

bytes and initializes the allocated bytes to zero.

For ordinary integer objects, this results in zero values.

Key takeaway

calloc(n, size) allocates space for n objects and zero-initializes the allocated bytes.


Chapter 20 — realloc

Question

What is the purpose of realloc here?

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

int main(void)
{
    int *numbers = malloc(2 * sizeof *numbers);

    if (numbers == NULL) {
        return 1;
    }

    numbers[0] = 10;
    numbers[1] = 20;

    // Request space for 4 ints.
    int *tmp = realloc(numbers, 4 * sizeof *numbers);

    if (tmp == NULL) {
        // Original allocation is still valid here.
        free(numbers);
        return 1;
    }

    numbers = tmp;

    numbers[2] = 30;
    numbers[3] = 40;

    printf("%d %d %d %d\n",
           numbers[0],
           numbers[1],
           numbers[2],
           numbers[3]);

    free(numbers);

    return 0;
}

Answer

10 20 30 40

Step-by-step explanation

Initially:

capacity = 2

Then:

realloc(numbers, 4 * sizeof *numbers)

requests enough space for four integers.

It may:

  • enlarge the existing allocation, or

  • allocate a new region and copy the old contents.

This is why using a temporary pointer is safer:

int *tmp = realloc(...);

Do not blindly do:

numbers = realloc(numbers, ...);

because if realloc fails, you could lose the original pointer.

Key takeaway

Use a temporary pointer when calling realloc.


Chapter 21 — free

Question

What is wrong with this?

#include <stdlib.h>

int main(void)
{
    int *p = malloc(sizeof *p);

    if (p == NULL) {
        return 1;
    }

    *p = 10;

    free(p);

    printf("%d\n", *p);

    return 0;
}

Answer

The program accesses memory after it has been freed.

That is use-after-free and therefore undefined behavior.

After:

free(p);

think:

p → no longer owns a valid allocated object

A useful practice is:

free(p);
p = NULL;

when the pointer remains in scope and might otherwise accidentally be reused.

Key takeaway

After free, the allocated object no longer exists.


Module 9 — Memory Bugs & Ownership

Chapter 22 — Memory Leak

Question

What is wrong here?

#include <stdlib.h>

int main(void)
{
    int *p = malloc(sizeof *p);

    if (p == NULL) {
        return 1;
    }

    *p = 100;

    // Program loses the only pointer to the allocation.
    p = NULL;

    return 0;
}

Answer

The allocated memory is leaked.

Step-by-step explanation

Initially:

p → heap allocation

Then:

p = NULL;

Now:

p → NULL

heap allocation → unreachable

There is no way to call:

free(...)

on that allocation anymore.

Correct:

free(p);
p = NULL;

Key takeaway

Every successful allocation needs a clear path to free.


Chapter 23 — Double Free

Question

What is wrong?

#include <stdlib.h>

int main(void)
{
    int *p = malloc(sizeof *p);

    if (p == NULL) {
        return 1;
    }

    free(p);
    free(p);

    return 0;
}

Answer

p is freed twice.

This is double free, which is undefined behavior.

Safer:

free(p);
p = NULL;

Then:

free(p);

is valid because free(NULL) is defined to do nothing.

Key takeaway

Free an allocation exactly once.


Chapter 24 — Ownership and Lifetime

Question

Who should free this memory?

int *create_number(void)
{
    int *p = malloc(sizeof *p);

    if (p != NULL) {
        *p = 42;
    }

    return p;
}

Answer

The caller that receives the returned pointer now needs to manage the allocation.

For example:

int *p = create_number();

if (p != NULL) {
    printf("%d\n", *p);
    free(p);
}

Step-by-step explanation

A useful mental model is ownership.

create_number() creates the allocation.

The caller receives it:

create_number()
       │
       ▼
     heap
       ▲
       │
     caller

The caller should know:

"I received this allocated memory, so I am responsible for releasing it."

C does not automatically enforce ownership.

Key takeaway

In C, programmers must deliberately define who owns and frees dynamically allocated memory.


Module 10 — Complex Pointer Concepts

Chapter 25 — const With Pointers

Question

Which operations are allowed?

int x = 10;
int y = 20;

const int *p = &x;

// *p = 30;       // ❌ not allowed through p
p = &y;           // ✅ allowed

Answer

Here:

const int *p;

means:

p points to an int that cannot be modified through this pointer.

It does not mean the pointer itself cannot move.

Therefore:

p = &y;

is allowed.


There are three important forms:

const int *p;

Pointer to const int.

int *const p = &x;

Const pointer to int.

const int *const p = &x;

Const pointer to const int.

Key takeaway

With pointers, determine separately whether the pointed-to object or the pointer itself is const.


Chapter 26 — Pointer to Array

Question

What does this declaration mean?

int (*p)[3];

Answer

It means:

p is a pointer to an array of 3 integers.

Compare:

int *p[3];

which means:

p is an array of 3 pointers to integers.

Parentheses matter enormously.

int (*p)[3]
     ↑
 pointer to array


int *p[3]
        ↑
 array of pointers

Key takeaway

In C declarations, parentheses can completely change what a pointer declaration means.


Chapter 27 — Pointer Casting

Question

What is happening here?

int x = 42;

void *p = &x;

int *ip = (int *)p;

Answer

The address stored in p is being converted from:

void *

to:

int *

so it can be interpreted as pointing to an int.

However:

A cast does not magically make an invalid pointer valid.

The underlying address must actually be suitable for the target interpretation.

Key takeaway

Casting a pointer changes how C interprets the pointer; it does not repair invalid memory.


Module 11 — Undefined Behavior ⭐

Chapter 28 — What Is Undefined Behavior?

Question

What is wrong with this?

#include <stdio.h>

int main(void)
{
    int numbers[3] = {10, 20, 30};

    printf("%d\n", numbers[5]);

    return 0;
}

Answer

numbers[5] is outside the array.

The program has undefined behavior.

It might:

  • print a strange number

  • appear to work

  • crash

  • behave differently after optimization

  • produce completely unexpected results

The crucial point is:

C does not promise what will happen.

Key takeaway

Undefined behavior means you cannot safely reason from the program's expected result.


Chapter 29 — Out-of-Bounds Pointer Arithmetic

Question

Is this valid?

int numbers[3] = {10, 20, 30};

int *p = numbers;

p = p + 3;

Answer

This is an important subtlety.

A pointer may legally point one past the end of an array:

numbers:

[10] [20] [30] [one-past]
 ↑
 p

So:

p + 3

can form the one-past pointer.

But this is not valid:

*p

after p has become one-past.

Key takeaway

One-past-the-end pointers may be formed and compared, but must not be dereferenced.


Module 12 — Memory Layout & Stack vs Heap

Chapter 30 — Stack vs Heap

Question

Where do these objects conceptually live?

#include <stdlib.h>

int main(void)
{
    int x = 10;

    int *p = malloc(sizeof *p);

    if (p == NULL) {
        return 1;
    }

    *p = 20;

    free(p);

    return 0;
}

Answer

Conceptually:

Stack
──────────────
x
p
──────────────

Heap
──────────────
allocated int
──────────────

x is an automatic local object.

The memory obtained by malloc is dynamically allocated storage.

Important distinction

Do not think:

"Every pointer is on the heap."

p itself is a local variable and commonly lives in the stack frame.

The object p points to is the dynamically allocated memory.

Key takeaway

A pointer and the object it points to are two separate objects with potentially different lifetimes and storage locations.


Chapter 31 — Stack Frames

Question

What happens conceptually when this executes?

int add(int a, int b)
{
    int result = a + b;

    return result;
}

int main(void)
{
    int x = add(10, 20);

    return 0;
}

Answer

Conceptually, a call to add() creates a new execution context containing things such as:

add stack frame
──────────────
a
b
result
return information
──────────────

Then the function returns.

Its local automatic objects cease to exist.

This explains why returning:

return &result;

would be invalid.

Key takeaway

Local variables have lifetimes tied to their execution scope/storage duration.


Module 13 — Advanced Pointer Concepts

Chapter 32 — Callbacks

Question

What is printed?

#include <stdio.h>

int square(int x)
{
    return x * x;
}

int apply(int value, int (*operation)(int))
{
    // Call whichever function was supplied.
    return operation(value);
}

int main(void)
{
    printf("%d\n", apply(5, square));

    return 0;
}

Answer

25

Step-by-step explanation

apply() receives a function pointer:

int (*operation)(int)

Then:

operation(value)

calls the supplied function.

So:

apply
 ↓
square
 ↓
25

This is a callback pattern.

Key takeaway

Function pointers allow behavior to be passed into functions.


Chapter 33 — Pointer Aliasing

Question

What happens here?

int x = 10;

int *a = &x;
int *b = &x;

*a = 50;

printf("%d\n", *b);

Answer

50

Step-by-step explanation

Both pointers point to the same object:

      ┌──────┐
a ───►│      │
      │  10  │
b ───►│      │
      └──────┘

After:

*a = 50;

the object becomes 50.

Therefore *b also sees 50.

This relationship is called aliasing.

Key takeaway

Multiple pointers can refer to the same object.


Chapter 34 — restrict

Question

What is the purpose of restrict here?

void add_arrays(
    int *restrict out,
    const int *restrict a,
    const int *restrict b,
    int n
)
{
    for (int i = 0; i < n; i++) {
        out[i] = a[i] + b[i];
    }
}

Answer

restrict tells the compiler that, under the programmer's contract, the objects accessed through these restricted pointers are not being accessed through competing pointer paths in a conflicting way.

This can enable optimization.

But it creates a programmer responsibility.

If you violate the restrictions, behavior can become undefined.

Key takeaway

restrict is an optimization-related promise about pointer aliasing, not a magical performance switch.


Module 14 — The Big Picture

Chapter 35 — Putting Pointers and Dynamic Memory Together

Question

What does this program ultimately print?

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

void double_values(int *numbers, int count)
{
    // Modify each heap element through the pointer.
    for (int i = 0; i < count; i++) {
        numbers[i] *= 2;
    }
}

int main(void)
{
    int count = 3;

    // Allocate an array on the heap.
    int *numbers = malloc(count * sizeof *numbers);

    if (numbers == NULL) {
        return 1;
    }

    // Initialize the dynamically allocated array.
    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;

    // Pass the address of the first element.
    double_values(numbers, count);

    printf("%d %d %d\n",
           numbers[0],
           numbers[1],
           numbers[2]);

    // Release the heap allocation.
    free(numbers);

    return 0;
}

Answer

20 40 60

Step-by-step explanation

The important chain is:

malloc
  ↓
heap memory
  ↓
numbers
  ↓
pointer to first element
  ↓
double_values()
  ↓
numbers[i] *= 2
  ↓
same heap objects modified
  ↓
free()

This one program combines:

  • pointers

  • arrays

  • pointer/function interaction

  • dynamic allocation

  • malloc

  • NULL checking

  • dereferencing

  • pointer-based modification

  • ownership

  • free

Key takeaway

This is the fundamental C pattern: allocate memory → access it through pointers → modify it → release it.


Phase 4 — Master Mental Model

At the end of Phase 4, you should be able to look at C code and mentally translate it like this:

int x = 10;

Create an integer object containing 10.

int *p = &x;

Create a pointer containing x's address.

*p

Access the integer located at that address.

*p = 20;

Modify that integer.

int **pp = &p;

Create a pointer to the pointer.

malloc(...)

Ask the allocator for dynamic storage.

free(p);

Release the dynamic allocation owned through p.


Phase 4 — The Pointer Map

Memorize this relationship:

                    ADDRESS
                       │
                       ▼
                 ┌───────────┐
                 │     p     │
                 │  address  │
                 └─────┬─────┘
                       │
                     *p
                       │
                       ▼
                 ┌───────────┐
                 │     x     │
                 │   value   │
                 └───────────┘

And for pointer-to-pointer:

┌──────┐       ┌──────┐       ┌──────┐
│  pp  │ ────► │  p   │ ────► │  x   │
│      │       │      │       │  10  │
└──────┘       └──────┘       └──────┘

 **pp = x
 *pp  = p
 pp   = address of p

Phase 4 — What You Must Now Be Able to Recognize

You should now recognize these immediately:

CodeMeaning
&xaddress of x
int *ppointer to int
*pvalue/object pointed to by p
*p = 5modify pointed-to object
p + 1next element for an appropriate array pointer
p->xstructure member through pointer
int **ppointer to pointer to int
void *pgeneric object pointer
malloc()dynamically allocate memory
calloc()dynamically allocate zero-initialized bytes
realloc()resize an allocation
free()release dynamic allocation
NULLnull pointer value
dangling pointerpointer to an object whose lifetime ended
wild pointeruninitialized/invalid pointer
memory leakallocated memory no longer reachable for freeing
double freesame allocation released twice
use-after-freeaccessing an object after it was freed
UBundefined behavior

The most important distinction

Never confuse these three:

p       → the pointer/address
*p      → the object/value at that address
&p      → the address of the pointer itself

For:

int x = 10;
int *p = &x;

you have:

x   = 10
p   = address of x
*p  = 10
&x  = address of x
&p  = address of p

That distinction is the core of C pointers.


Phase 4 Complete

You have now covered the full Phase 4 scope:

addresses → pointers → dereferencing → pointer types → arrays → pointer arithmetic → strings → function pointers → callbacks → **void * → NULL/wild/dangling pointers → structures → -> → stack/heap → malloccallocreallocfree → ownership/lifetime → memory leaks → double free → use-after-free → complex declarations → const pointers → casts → aliasing → restrict → undefined behavior → memory layout.

Phase 5 is not started.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.