Introduction to C Language | Lesson 6: Detailed Explanation of Pointers – Unveiling the Most Mysterious Aspect of C Language

Introduction to C Language | Lesson 6: Detailed Explanation of Pointers – Unveiling the Most Mysterious Aspect of C Language

1.🎯 Introduction: Why are Pointers So Important?

Hello everyone! Today we are going to learn about one of the most important and also the most headache-inducing concepts for beginners in C language β€” Pointers.

Many people say: “Once you master pointers, you have grasped the soul of C language.” However, pointers are also the “roadblock” that has discouraged countless C language learners. But don’t worry, today I will explain pointers in the simplest way possible!

Let’s start with an analogy to help you understand:

Imagine your home has an address (for example, “XX Street, Chaoyang District, Beijing”), and others need to know this address to find your home. In C language:

  • β€’ Your home = Variable
  • β€’ Your home’s address = Memory address of the variable
  • β€’ A notebook that records your home address = Pointer variable

A pointer is like a “notebook that records addresses”!

2.🧠 Core Concepts of Pointers

(1) What is a Memory Address?

The computer’s memory is like a huge apartment building, where each room (byte) has a unique door number (address).

// Assume the memory addresses are as follows (actual addresses are in hexadecimal)
Memory Address     Stored Data
0x1000      10
0x1004      20
0x1008      30
0x100C      40

(2) What is a Pointer?

A pointer is a special variable that does not store ordinary data, but rather the memory address of another variable.

int a = 50;        // Ordinary variable a, storing the value 50
int *p = &a;       // Pointer variable p, storing the address of a

// This can be understood as:
// a is a room, containing the number 50
// p is a piece of paper, on which the door number of room a is written

(3) Two Core Operators

int a = 100;       // Define an integer variable
int *p;            // Define a pointer variable

// Operator 1: & (Address-of operator)
p = &a;            // Get the address of a and assign it to p
// This can be understood as: p has written down the door number of a

// Operator 2: * (Dereference operator)
int value = *p;    // Find the variable pointed to by p and get its value
// This can be understood as: based on the door number recorded on p, find that room and see the data inside

3.πŸ“ Declaration and Usage of Pointers

(1) Syntax for Declaring Pointers

DataType *PointerVariableName;

// Example:
int *p1;       // Pointer to int type
char *p2;      // Pointer to char type
float *p3;     // Pointer to float type

Important Note:

  • β€’ <span>int *p</span> in which <span>*</span> is a declaration symbol indicating that p is a pointer
  • β€’ When using, <span>*p</span> is a dereference operation, indicating to get the value pointed to by p

(2) Complete Example: Basic Operations with Pointers

#include <stdio.h>

int main()
{
    // Step 1: Define an ordinary variable
    int a = 50;

    // Step 2: Define a pointer variable and initialize it
    int *p = &a;  // p points to a, meaning p stores the address of a

    // Step 3: View related information
    printf("=== Information about variable a ===\n");
    printf("Value of a: %d\n", a);              // Output: 50
    printf("Address of a: %p\n", &a);           // Output: address of a in memory (hexadecimal)
    printf("Bytes occupied by a: %lu\n", sizeof(a));  // Output: 4 (int type occupies 4 bytes)

    printf("\n=== Information about pointer p ===\n");
    printf("Value of p (i.e., address of a): %p\n", p);   // Output: same as &a
    printf("Address of p itself: %p\n", &p);       // Output: address of p itself in memory
    printf("Value pointed to by p: %d\n", *p);         // Output: 50 (accessing value of a through p)
    printf("Bytes occupied by p: %lu\n", sizeof(p));  // Output: 8 (64-bit system) or 4 (32-bit system)

    // Step 4: Modify the original variable's value through the pointer
    printf("\n=== Modifying value through pointer ===\n");
    *p = 100;  // Modify the value of a through pointer p
    printf("After modification, value of a: %d\n", a);      // Output: 100
    printf("After modification, value of *p: %d\n", *p);    // Output: 100

    // Step 5: Reassigning the pointer
    int b = 200;
    p = &b;  // Now p points to b
    printf("\n=== Pointer Reassignment ===\n");
    printf("p now points to b, *p = %d\n", *p);  // Output: 200

    return 0;
}

Example of Running Results:

=== Information about variable a ===
a's value: 50
a's address: 0x7ffeeb3c8a1c
a occupies bytes: 4

=== Information about pointer p ===
p's value (i.e., address of a): 0x7ffeeb3c8a1c
p's own address: 0x7ffeeb3c8a20
value pointed to by p: 50
p occupies bytes: 8

=== Modifying value through pointer ===
After modification, value of a: 100
After modification, value of *p: 100

=== Pointer Reassignment ===
p now points to b, *p = 200

(3) 🎨 Memory Diagram

Let’s use a diagram to understand the above code:

Initial State:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Variable a      β”‚
β”‚  Address: 0x1000 β”‚
β”‚  Value: 50       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↑
         β”‚ Points to
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Pointer p       β”‚
β”‚  Address: 0x2000 β”‚
β”‚  Value: 0x1000   β”‚  ← p stores the address of a
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

After executing *p = 100:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Variable a      β”‚
β”‚  Address: 0x1000 β”‚
β”‚  Value: 100 βœ“    β”‚  ← Modified a's value through pointer
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

4.πŸ”„ Pointers as Function Parameters: Pass by Value vs Pass by Pointer

This is a key application scenario for understanding pointers!

(1) Problem Scenario: Swapping Values of Two Variables

#include <stdio.h>

// Method 1: Pass by Value (failed attempt)
void swapByValue(int x, int y)
{
    printf("  [Inside Function] Before Swap: x=%d, y=%d\n", x, y);

    int temp = x;  // Temporarily save the value of x
    x = y;         // Assign y to x
    y = temp;      // Assign original x to y

    printf("  [Inside Function] After Swap: x=%d, y=%d\n", x, y);
    // Note: It seems to have swapped successfully, but only swapped the copies!
}

// Method 2: Pass by Pointer (successful method)
void swapByPointer(int *px, int *py)
{
    printf("  [Inside Function] Before Swap: *px=%d, *py=%d\n", *px, *py);

    int temp = *px;   // Temporarily save the value pointed to by px
    *px = *py;        // Assign the value pointed to by py to the location pointed to by px
    *py = temp;       // Assign the original value pointed to by px to the location pointed to by py

    printf("  [Inside Function] After Swap: *px=%d, *py=%d\n", *px, *py);
}

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

    // Test Pass by Value
    printf("=== Test Pass by Value ===\n");
    printf("[Main Function] Before Call: a=%d, b=%d\n", a, b);
    swapByValue(a, b);
    printf("[Main Function] After Call: a=%d, b=%d\n", a, b);  // No change!
    printf("Conclusion: Pass by Value cannot modify original variable\n");

    // Reset values of a and b
    a = 10; b = 20;

    // Test Pass by Pointer
    printf("\n=== Test Pass by Pointer ===\n");
    printf("[Main Function] Before Call: a=%d, b=%d\n", a, b);
    swapByPointer(&a, &b);  // Pass the addresses of a and b
    printf("[Main Function] After Call: a=%d, b=%d\n", a, b);  // Successfully swapped!
    printf("Conclusion: Pass by Pointer can modify original variable\n");

    return 0;
}

Running Results:

=== Test Pass by Value ===
[Main Function] Before Call: a=10, b=20
  [Inside Function] Before Swap: x=10, y=20
  [Inside Function] After Swap: x=20, y=10
[Main Function] After Call: a=10, b=20
Conclusion: Pass by Value cannot modify original variable

=== Test Pass by Pointer ===
[Main Function] Before Call: a=10, b=20
  [Inside Function] Before Swap: *px=10, *py=20
  [Inside Function] After Swap: *px=20, *py=10
[Main Function] After Call: a=20, b=10
Conclusion: Pass by Pointer can modify original variable

(2) πŸ€” Why Does This Happen? In-Depth Understanding

Process of Pass by Value:

In main function:
a = 10 (Address: 0x1000)
b = 20 (Address: 0x2000)

Calling swapByValue(a, b):
Creates copy x = 10 (Address: 0x3000) ← copied value of a
Creates copy y = 20 (Address: 0x4000) ← copied value of b

Inside the function, swapping x and y:
x = 20, y = 10
But this only modifies the copies! The original a and b remain unchanged.

At the end of the function, x and y are destroyed.
a and b still have their original values.

Process of Pass by Pointer:

In main function:
a = 10 (Address: 0x1000)
b = 20 (Address: 0x2000)

Calling swapByPointer(&a, &b):
px = 0x1000 (points to a)
py = 0x2000 (points to b)

Inside the function, modifying through pointers:
*px = *py      β†’ Change the value at 0x1000 to 20
*py = temp     β†’ Change the value at 0x2000 to 10

After the function ends:
a = 20 βœ“ (successfully modified)
b = 10 βœ“ (successfully modified)

5.🎯 Pointers and Arrays: A Natural Pair

(1) The Essence of Array Names

This is an important concept:The array name is a pointer to the first element of the array!

#include <stdio.h>

int main()
{
    int arr[5] = {10, 20, 30, 40, 50};

    // Verification: The array name is the address of the first element
    printf("=== Relationship between Array Name and Address ===\n");
    printf("Value of arr: %p\n", arr);           // Array name
    printf("Value of &arr[0]: %p\n", &arr[0]);   // Address of first element
    printf("Are they equal? %s\n", arr == &arr[0] ? "Yes!" : "No");

    // Use pointer to point to the array
    int *p = arr;  // Equivalent to int *p = &arr[0];

    printf("\n=== Accessing Array Elements with Pointer ===\n");
    printf("First Element: %d\n", *p);          // Output: 10
    printf("Second Element: %d\n", *(p + 1));    // Output: 20
    printf("Third Element: %d\n", *(p + 2));    // Output: 30

    return 0;
}

(2) Three Equivalent Ways to Access Array Elements

#include <stdio.h>

int main()
{
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("=== Three Equivalent Access Methods ===\n");
    printf("Accessing the 2nd Element (Index 1):\n");

    // Method 1: Traditional Array Indexing
    printf("  arr[1] = %d\n", arr[1]);

    // Method 2: Pointer Arithmetic (based on array name)
    printf("  *(arr + 1) = %d\n", *(arr + 1));

    // Method 3: Pointer Arithmetic (based on pointer variable)
    printf("  *(p + 1) = %d\n", *(p + 1));

    // This is also valid (not recommended, but legal)
    printf("  p[1] = %d\n", p[1]);

    printf("\nAll methods yield the same result!\n");

    return 0;
}

(3) πŸ” Detailed Explanation of Pointer Arithmetic

Pointer addition and subtraction are based onthe size of the data type, not by bytes!

#include <stdio.h>

int main()
{
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("=== Demonstration of Pointer Arithmetic ===\n");
    printf("Size of int type in bytes: %lu\n", sizeof(int));

    printf("\nPointer Position Changes:\n");
    printf("p points to arr[0], address: %p, value: %d\n", p, *p);

    p++;  // Pointer moves forward by the size of one int (4 bytes)
    printf("After p++, points to arr[1], address: %p, value: %d\n", p, *p);

    p += 2;  // Move forward by the size of two ints (8 bytes)
    printf("After p+=2, points to arr[3], address: %p, value: %d\n", p, *p);

    p--;  // Move back by one int
    printf("After p--, points to arr[2], address: %p, value: %d\n", p, *p);

    // Verify address difference
    p = arr;
    printf("\n=== Address Difference Calculation ===\n");
    printf("Byte difference from &arr[0] to &arr[1]: %ld\n", 
           (char*)&arr[1] - (char*)&arr[0]);
    printf("Difference in elements between p+1 and p: %ld\n", (p+1) - p);

    return 0;
}

(4) Traversing Arrays with Pointers

#include <stdio.h>

int main()
{
    int scores[] = {85, 92, 78, 96, 88};
    int size = sizeof(scores) / sizeof(scores[0]);  // Calculate number of array elements

    // Method 1: Using Pointer + Offset
    printf("=== Method 1: Pointer + Offset ===\n");
    int *p = scores;
    for(int i = 0; i < size; i++)
    {
        printf("scores[%d] = %d (address: %p)\n", i, *(p + i), p + i);
    }

    // Method 2: Moving the Pointer Itself
    printf("\n=== Method 2: Moving Pointer ===\n");
    p = scores;  // Reset pointer to starting position
    int *end = scores + size;  // Point to the next position after the end of the array
    int index = 0;

    while(p < end)  // While pointer has not gone out of bounds
    {
        printf("scores[%d] = %d\n", index, *p);
        p++;      // Move pointer to the next element
        index++;
    }

    // Method 3: Reverse Traversal
    printf("\n=== Method 3: Reverse Traversal ===\n");
    p = scores + size - 1;  // Point to the last element
    for(int i = size - 1; i >= 0; i--)
    {
        printf("scores[%d] = %d\n", i, *p);
        p--;  // Move pointer back
    }

    return 0;
}

6.⚠️ Common Pointer Errors and Prevention

(1) Error 1: Wild Pointer (Most Dangerous!)

#include <stdio.h>

int main()
{
    // Error Example: Uninitialized Pointer
    int *p1;  // p1 is a wild pointer, pointing to unknown memory
    // *p1 = 10;  // ❌ Dangerous! May cause program crash

    // Correct Approach 1: Initialize to NULL
    int *p2 = NULL;  // NULL indicates a null pointer, not pointing to any valid memory

    // Correct Approach 2: Initialize to point to a valid variable
    int a = 100;
    int *p3 = &a;  // p3 points to valid variable a

    // Check before use
    if(p2 != NULL)  // Check if pointer is not null
    {
        *p2 = 20;  // Only operate if not null
    }
    else
    {
        printf("p2 is a null pointer, cannot dereference\n");
    }

    // Safely use p3
    if(p3 != NULL)
    {
        printf("Value pointed to by p3: %d\n", *p3);
    }

    return 0;
}

(2) Error 2: Dereferencing Null Pointer

#include <stdio.h>

int main()
{
    int *p = NULL;

    // Error Example
    // *p = 10;  // ❌ Null pointer cannot be dereferenced, program will crash

    // Correct Approach: Check before use
    if(p != NULL)
    {
        *p = 10;  // Only dereference if not null
        printf("Assignment successful: %d\n", *p);
    }
    else
    {
        printf("Pointer is null, cannot assign\n");
    }

    // Safer approach: Allocate memory or point to a valid variable first
    int value = 0;
    p = &value;  // Now p points to valid memory
    *p = 10;     // Safe
    printf("Now can assign: %d\n", value);

    return 0;
}

(3) Error 3: Pointer Type Mismatch

#include <stdio.h>

int main()
{
    int a = 100;

    // Error Example: Type Mismatch
    // char *p = &a;  // ❌ Warning: Assigning int* to char*

    // Correct Approach: Type Matching
    int *p_int = &a;          // βœ… int pointer pointing to int variable
    char c = 'A';
    char *p_char = &c;        // βœ… char pointer pointing to char variable

    printf("int pointer: %d\n", *p_int);
    printf("char pointer: %c\n", *p_char);

    // If type conversion is indeed needed, use explicit type casting
    char *p_force = (char*)&a;  // Force conversion, but understand the consequences
    printf("Accessing first byte after force conversion: %d\n", *p_force);

    return 0;
}

(4) Error 4: Dangling Pointer

#include <stdio.h>

int* dangerousFunction()
{
    int local = 100;  // Local variable
    return &local;    // ❌ Dangerous! Returning address of local variable
}  // After function ends, local is destroyed, pointer becomes dangling

int main()
{
    int *p = dangerousFunction();
    // Dereferencing *p is undefined behavior! May crash or output garbage value

    // Correct Approach: Return global variable, static variable, or dynamically allocated memory
    return 0;
}

7.🎯 Practical Project: Pointer Applications

(1) Project 1: Find Maximum and Minimum Values in an Array

#include <stdio.h>

// Find maximum value, return pointer to maximum value
int* findMax(int arr[], int size)
{
    if(size <= 0) return NULL;  // Array is empty, return NULL

    int *maxPtr = &arr[0];  // Assume first element is the largest

    for(int i = 1; i < size; i++)
    {
        if(arr[i] > *maxPtr)  // If current element is larger
        {
            maxPtr = &arr[i];  // Update pointer to maximum value
        }
    }

    return maxPtr;
}

// Find minimum value, return pointer to minimum value
int* findMin(int arr[], int size)
{
    if(size <= 0) return NULL;

    int *minPtr = &arr[0];

    for(int i = 1; i < size; i++)
    {
        if(arr[i] < *minPtr)
        {
            minPtr = &arr[i];
        }
    }

    return minPtr;
}

int main()
{
    int numbers[] = {45, 23, 67, 12, 89, 34, 56};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    // Display original array
    printf("=== Original Array ===\n");
    for(int i = 0; i < size; i++)
    {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    // Find maximum value
    int *maxPtr = findMax(numbers, size);
    if(maxPtr != NULL)
    {
        printf("\n=== Maximum Value Information ===\n");
        printf("Maximum Value: %d\n", *maxPtr);
        printf("Address of Maximum Value: %p\n", maxPtr);
        printf("Index of Maximum Value: %ld\n", maxPtr - numbers);
    }

    // Find minimum value
    int *minPtr = findMin(numbers, size);
    if(minPtr != NULL)
    {
        printf("\n=== Minimum Value Information ===\n");
        printf("Minimum Value: %d\n", *minPtr);
        printf("Address of Minimum Value: %p\n", minPtr);
        printf("Index of Minimum Value: %ld\n", minPtr - numbers);
    }

    // Modify values through pointers
    printf("\n=== Modifying Maximum and Minimum Values ===\n");
    *maxPtr = 100;  // Change maximum value to 100
    *minPtr = 0;    // Change minimum value to 0

    printf("Modified Array:\n");
    for(int i = 0; i < size; i++)
    {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    return 0;
}

(2) Project 2: Reverse a String Using Pointers

#include <stdio.h>

// Find maximum value, return pointer to maximum value
int* findMax(int arr[], int size)
{
    if(size <= 0) return NULL;  // Array is empty, return NULL

    int *maxPtr = &arr[0];  // Assume first element is the largest

    for(int i = 1; i < size; i++)
    {
        if(arr[i] > *maxPtr)  // If current element is larger
        {
            maxPtr = &arr[i];  // Update pointer to maximum value
        }
    }

    return maxPtr;
}

// Find minimum value, return pointer to minimum value
int* findMin(int arr[], int size)
{
    if(size <= 0) return NULL;

    int *minPtr = &arr[0];

    for(int i = 1; i < size; i++)
    {
        if(arr[i] < *minPtr)
        {
            minPtr = &arr[i];
        }
    }

    return minPtr;
}

int main()
{
    int numbers[] = {45, 23, 67, 12, 89, 34, 56};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    // Display original array
    printf("=== Original Array ===\n");
    for(int i = 0; i < size; i++)
    {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    // Find maximum value
    int *maxPtr = findMax(numbers, size);
    if(maxPtr != NULL)
    {
        printf("\n=== Maximum Value Information ===\n");
        printf("Maximum Value: %d\n", *maxPtr);
        printf("Address of Maximum Value: %p\n", maxPtr);
        printf("Index of Maximum Value: %ld\n", maxPtr - numbers);
    }

    // Find minimum value
    int *minPtr = findMin(numbers, size);
    if(minPtr != NULL)
    {
        printf("\n=== Minimum Value Information ===\n");
        printf("Minimum Value: %d\n", *minPtr);
        printf("Address of Minimum Value: %p\n", minPtr);
        printf("Index of Minimum Value: %ld\n", minPtr - numbers);
    }

    // Modify values through pointers
    printf("\n=== Modifying Maximum and Minimum Values ===\n");
    *maxPtr = 100;  // Change maximum value to 100
    *minPtr = 0;    // Change minimum value to 0

    printf("Modified Array:\n");
    for(int i = 0; i < size; i++)
    {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    return 0;
}

8.πŸ“š In-Depth Comparison of Pointers and Arrays

#include <stdio.h>

int main()
{
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("=== Array Name vs Pointer Variable ===\n");

    // Similarity 1: Both can be accessed via index
    printf("arr[2] = %d\n", arr[2]);
    printf("p[2] = %d\n", p[2]);

    // Similarity 2: Both can perform pointer arithmetic
    printf("*(arr + 2) = %d\n", *(arr + 2));
    printf("*(p + 2) = %d\n", *(p + 2));

    // Difference 1: Array name is a constant, cannot be modified
    // arr = arr + 1;  // ❌ Compilation error! Array name cannot be modified
    p = p + 1;  // βœ… Correct! Pointer variable can be modified
    printf("After moving p: *p = %d\n", *p);  // Now points to arr[1]

    // Difference 2: sizeof behaves differently
    p = arr;  // Reset pointer
    printf("\nsizeof(arr) = %lu (size of entire array)\n", sizeof(arr));
    printf("sizeof(p) = %lu (size of pointer variable)\n", sizeof(p));

    // Calculate number of array elements
    int arrSize = sizeof(arr) / sizeof(arr[0]);
    printf("Number of array elements: %d\n", arrSize);

    return 0;
}

9.🎯 Conclusion

Pointers are a core feature of C language, and while they may seem difficult at first, they are the key to understanding the underlying workings of computers. Through this lesson, you should master:

  • β€’ βœ… Basic concepts and operations of pointers
  • β€’ βœ… Pointers and function parameter passing
  • β€’ βœ… Relationship between pointers and arrays
  • β€’ βœ… Identification and avoidance of common errors
  • β€’ βœ… Practical applications of pointers

Remember: Pointers are not magic; they are just variables that store addresses. Once you understand the concept of memory addresses, pointers will no longer be mysterious. Write more code, debug, and think critically, and you will surely master pointers!

If you found this helpful, remember to like, bookmark, and share! Feel free to discuss any questions in the comments!

Leave a Comment