Master C Pointers: Beginner to Intermediate Guide with Diagrams & Code Examples

目次

1. Introduction

When learning the C language, understanding “pointer variables” is something you can’t avoid. For beginners, concepts like “addresses” or “indirect referencing” might seem difficult, but pointers are a fundamental and important element of the C language, and mastering them enables more advanced programming.

This article will explain step by step, from the basics of “What is a pointer variable?” to practical code examples, as well as relationships with arrays and functions, and advanced usage. To help you easily grasp the meaning of technical terms and the imagery of operations, we’ll include diagrams and sample code along the way, so even those not yet familiar with C can read on with confidence.

Pointers, once you get used to them, are extremely powerful and convenient features. They greatly expand the scope of your programs, such as through dynamic memory operations or passing values to functions. Through this article, I hope you deepen your understanding of pointer variables and elevate your C language development skills to the next level.

2. What is a Pointer Variable?

Basic Concepts of Pointer Variables

A pointer variable in C is a “variable that stores a memory address“. Unlike regular variables that store the data itself, pointers store the address of where another variable is located.

For example, consider the following code.

int a = 10;
int *p = &a

In this case, the variable a stores the value 10, and p stores the address of a. By writing *p, you can indirectly reference the value at the address pointed to by p (in this case, 10).

The Relationship Between Addresses and Memory

All data is stored in the computer’s memory, which has addresses assigned to each byte. Pointers use this address information to specify which memory location to manipulate.

By using pointers, the following becomes possible.

  • Directly modify the contents of variables between functions
  • Flexibly manipulate array elements
  • Dynamic memory management using heap memory

In other words, pointers are an important mechanism that supports the flexibility and low-level control of C.

Declaration and Initialization of Pointer Variables

Pointer variables are declared by attaching an asterisk (*) to the target data type.

int *p;   // Pointer to an int variable
char *c;  // Pointer to a char variable

And usually, the address of another variable is assigned using the & operator.

int a = 5;
int *p = &a  // Store a's address in p

What’s important here is that the “type of the pointer” and the “type of the value it points to” need to match. Storing a char-type address in a pointer for an int type may result in undefined behavior.

3. Basic Pointer Operations

Once you understand the basics of pointer variables, let’s take a concrete look at “how to use them.” Here, we introduce basic operation methods that are indispensable for pointer operations, such as operators, reading and writing values, and arithmetic between pointers.

Address Operator (&) and Indirection Operator (*)

Address Operator (&)

& is called the “address operator” and is used to obtain the memory address of a variable.

int a = 10;
int *p = &a

In this example, the address of the variable a is stored in p. p stores the “location where a is stored.”

Indirection Operator (*)

* is called the “indirection operator” or “dereference operator” and is used when referencing or modifying the contents of the address pointed to by the pointer.

int a = 10;
int *p = &a

printf("%d\n", *p);  // Result: 10

In this way, by writing *p, you can indirectly obtain the value (contents) of a. Conversely, you can also change the value by writing as follows.

*p = 20;
printf("%d\n", a);  // Result: 20

Obtaining and Modifying Values: Pointer Usage Examples

By using pointers, you can directly modify the value of a variable from another function. The following is a basic example.

void updateValue(int *p) {
    *p = 100;
}

int main() {
    int num = 50;
    updateValue(#);
    printf("%d\n", num);  // Result: 100
    return 0;
}

In this way, pointers are also useful when updating values inside functions. In C language, when passing values to functions, it is basically by copy (pass by value), but by using pointers, it becomes possible to manipulate the original value itself.

Pointer Addition and Subtraction

Pointers can be added and subtracted. This is very convenient when handling arrays or contiguous memory areas.

int arr[3] = {10, 20, 30};
int *p = arr;

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

The important point here is that when you do “p++“, it moves to the address of the next int-type variable. If the int type is 4 bytes, p++ adds “4” to the address.

In this way, understanding the basic operations of pointers is the first step in building the foundation for memory operations in C language. In the next chapter, let’s look in more detail at the relationship between pointers and arrays.

4. Relationship Between Arrays and Pointers

In C language, arrays and pointers have a very close relationship. This is a point that can be confusing for beginners, but understanding this relationship allows for more flexible and efficient array operations.

Array Names Can Be Treated Like Pointers

In C language, the array name is treated as a pointer to the address of the first element. For example, let’s look at code like the following.

int arr[3] = {10, 20, 30};
printf("%d\n", *arr);     // Result: 10

At this time, arr points to the same address as &arr[0], and *arr means the first element of the array (arr[0]).

Accessing Arrays Using Pointers

Arrays can be accessed using indices, but the same operations are possible using pointers.

int arr[3] = {10, 20, 30};
int *p = arr;

printf("%d\n", p[1]);     // Result: 20

Here, p[1] has the same meaning as *(p + 1) . In other words, using a pointer, you can also write it like this.

printf("%d\n", *(arr + 2));   // Result: 30

In this way, index notation and pointer arithmetic are essentially doing the same thing.

Differences Between Pointer Arithmetic and Array Indices

Arrays and pointers are similar, but it is also important to note that they are not exactly the same.

1. Obtaining Size

When using an array name, you can obtain its size with sizeof, but once assigned to a pointer, the size is lost.

int arr[5];
int *p = arr;

printf("%zu\n", sizeof(arr)); // Result: 20 (5×4 bytes)
printf("%zu\n", sizeof(p));   // Result: 8 (on 64-bit environments, pointers are 8 bytes)

2. Assignability

The array name is like a constant pointer and cannot be changed by assignment.

int arr[3];
int *p = arr;
p = p + 1;     // OK
arr = arr + 1; // Error (array name cannot be reassigned)

Benefits of Mastering Pointers and Arrays

  • Using pointers allows you to flexibly manipulate memory space
  • Array processing becomes faster and more efficient (pointer arithmetic can be slightly faster than indices in some cases)
  • When passing arrays to functions, only the starting address is passed, not the actual array, so knowledge of pointers is essential

5. Functions and Pointers

In C, when passing variables to functions, pass by value is the basic method. Therefore, even if the argument is modified inside the function, it does not affect the original variable. However, by using pointers, it becomes possible to directly manipulate the value of the original variable from the function.

This chapter explains the relationship between functions and pointers, how to modify values, the basics of function pointers, and other ways to use pointers in functions.

Modifying Values Using Pointers

First, if you want to change the value of a variable from inside a function, you need to use a pointer.

Example: Without a Pointer

void update(int x) {
    x = 100;
}

int main() {
    int a = 10;
    update(a);
    printf("%d\n", a); // Result: 10 (unchanged)
    return 0;
}

In this case, a copy of a is passed to the function, so a itself is not changed.

Example: Using a Pointer

void update(int *x) {
    *x = 100;
}

int main() {
    int a = 10;
    update(&a);
    printf("%d\n", a); // Result: 100 (changed)
    return 0;
}

In this way, by passing the address of the variable to the function, you can modify the original value. This is widely used as a technique for “pass by reference.”

The Relationship Between Arrays and Functions

In C, when an array is passed to a function, it is automatically treated as a pointer. In other words, the address of the first element of the array is passed to the function, so the contents can be modified inside the function.

void setValues(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
}

int main() {
    int nums[3];
    setValues(nums, 3);
    printf("%d\n", nums[1]); // Result: 10
    return 0;
}

As in this example, you can modify the contents from inside the function just by passing the array name as is.

Basics of Function Pointers

In C, you can store the address of a function in a variable and call it. This is a function pointer.

Declaration and Usage Example:

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

int main() {
    int (*funcPtr)(int, int); // Function pointer that returns int and takes two int arguments
    funcPtr = add;

    int result = funcPtr(3, 4);
    printf("%d\n", result); // Result: 7
    return 0;
}

Function pointers are used for cases where you want to dynamically select and execute functions, or for implementing callback functions. Since you can obtain the function’s address just by using the function name, flexible programming is possible.

Practical Usage Scenarios

  • Array sorting, passing comparison functions via pointers
  • Menu selection programs, managing functions to execute for each option with function pointers
  • Event-driven processing or callback functions (GUI, embedded systems, game development, etc.)

6. Pointer Applications

Once you understand the basic usage of pointers, let’s learn about advanced usage methods next. Pointers in the C language are essential for dynamic memory operations and implementing advanced data structures. In this chapter, we introduce three advanced techniques that are frequently used in practical work.

Dynamic Memory Allocation (malloc and free)

In the C language, you can dynamically allocate the memory needed at runtime. The malloc function from the standard library makes this possible. The allocated memory is referenced by a pointer, and it must be freed with free when you’re done using it.

Example: Dynamically Allocating an Integer

#include 
#include 

int main() {
    int *p = (int *)malloc(sizeof(int));  // Allocate memory for one int
    if (p == NULL) {
        printf("Failed to allocate memory\n");
        return 1;
    }

    *p = 123;
    printf("%d\n", *p);  // Output: 123

    free(p);  // Free the memory
    return 0;
}

Notes:

  • malloc returns the starting address of the allocated memory
  • Always check the return value for NULL
  • After using the memory, always free it with free

In this way, the appeal of dynamic memory management using pointers is that you can allocate only the memory you need at the right time.

Pointer to Pointer (Double Pointer)

A pointer that holds the address of another pointer, or a “pointer to a pointer,” is also commonly used in the C language. It is particularly useful when you want to modify a pointer inside a function or when operating on two-dimensional arrays.

Example: Initializing a Pointer Inside a Function

void allocate(int **pp) {
    *pp = (int *)malloc(sizeof(int));
    if (*pp != NULL) {
        **pp = 42;
    }
}

int main() {
    int *p = NULL;
    allocate(&p);
    printf("%d\n", *p);  // Output: 42
    free(p);
    return 0;
}

Common Use Cases:

  • Array of Arrays (Two-Dimensional Arrays)
  • When a Structure Contains a Variable-Length Array
  • Handling Multiple Strings (e.g., char *argv)

Combining Function Pointers and Arrays

Storing function pointers in an array and dynamically switching and calling functions based on the situation is also a common advanced technique in the C language.

Example: Simple Menu Selection

#include 

void hello() { printf("Hello"); }
void bye()   { printf("Goodbye"); }

int main() {
    void (*funcs[2])() = {hello, bye};

    int choice = 0;
    printf("0: hello, 1: bye > ");
    scanf("%d", &choice);

    if (choice >= 0 && choice < 2) {
        funcs[choice]();  // Function call
    }
    return 0;
}

This kind of design is also useful for state management and event-driven programs.

Advanced techniques using pointers require not just coding ability, but also a strong awareness of memory and execution control in design. However, once you master them, you can fully unleash the power of the C language.

7. Common Errors and Their Solutions

Pointers are a very powerful feature, but misusing them can cause bugs and security vulnerabilities. This chapter explains common errors that occur when using pointers in C and measures to prevent them.

Using Uninitialized Pointers

The most basic yet dangerous case is using an uninitialized pointer. Simply declaring a pointer does not make it point to a valid address.

Bad Example:

int *p;
*p = 10;  // Undefined behavior! p doesn't point anywhere

Solution:

  • Always initialize pointers before using them
  • Perform a NULL check before using them
int *p = NULL;
// Allocate memory or assign a valid address before using

Dereferencing a NULL Pointer

Dereferencing NULL with *p when the pointer points to NULL will cause the program to crash. This is a very common bug.

Example:

int *p = NULL;
printf("%d
", *p);  // Runtime error (segmentation fault, etc.)

Solution:

  • Check that the pointer is not NULL before using it
if (p != NULL) {
    printf("%d
", *p);
}

Memory Leaks

Forgetting to free dynamically allocated memory causes a memory leak where memory accumulates without being released. This is fatal in long-running programs or embedded systems.

Example:

int *p = (int *)malloc(sizeof(int));
// Forgetting to free after processing → Memory leak

Solution:

  • Always free after use
  • malloc and free should correspond to each other
  • Use memory leak detection tools (e.g., Valgrind) during development

Dangling Pointers

A pointer that points to memory after it has been freed is called a “dangling pointer” and can cause undefined behavior if reused.

Example:

int *p = (int *)malloc(sizeof(int));
free(p);
*p = 123;  // Error! Accessing already freed memory

Solution:

  • free After free, always assign NULL to invalidate it
free(p);
p = NULL;

Out-of-Bounds Array AccessIndex operations using pointers can

accidentally exceed array bounds

. This is also very dangerous and can cause bugs or vulnerabilities.Example:

int arr[3] = {1, 2, 3};
printf("%d
", *(arr + 3));  // Undefined behavior (arr[3] does not exist)

Solution:

  • Always

    confirm access is within valid bounds

  • Thoroughly perform “bounds checking” in loop processing

Double Freeing the Same PointerPerforming free on the same memory address twice can cause the program to crash.

Solution:

  • free By setting the pointer to NULL after free,

prevent double free

free(p);
p = NULL;

These errors can be prevented by following the basics and coding carefully

. Especially for beginners, adhering to rules like “initialization,” “NULL checks,” and “thorough freeing” leads to bug-free code.基本を守って丁寧にコーディングすることで防止可能です。特に初心者のうちは、「初期化」「NULLチェック」「freeの徹底」をルールとして守ることが、バグのないコードにつながります。

8. Summary

In C language, pointer variables are the most basic yet profoundly deep and important elements. This article has explained step by step from the basics of “What is a pointer?” to advanced examples such as arrays, functions, memory management, and function pointers.

Review of Key Points Learned

  • Pointer variables are variables that store the address of data, and are manipulated by * (indirection operator) and & (address operator)
  • Arrays and pointers are closely related, and array names can be treated as pointers indicating the starting address
  • By combining functions and pointers, “pass by reference” that directly manipulates variables within functions becomes possible, and flexible function calls can also be achieved using function pointers
  • Techniques such as dynamic memory management (malloc/free) and double pointers support more practical and flexible program design
  • On the other hand, uninitialized pointers, NULL references, memory leaks, and dangling pointers are common pointer-specific errors, requiring careful handling

Advice for Beginners

Pointers often give the impression of being “difficult” or “scary,” but that’s because they are used as black boxes. By thoroughly understanding the meaning of addresses and the mechanics of memory, that anxiety will turn into confidence.

It would be good to solidify your learning with the following steps:

  • Trace sample code by hand on paper with diagrams
  • printf to visualize and verify addresses and values
  • Valgrind and other memory check tools
  • Write multiple small pointer operation practice programs

Next Steps

The content introduced in this article covers C language pointers from beginner to intermediate levels. To deepen your understanding further, it would be good to move on to the following topics.

  • Structures and pointers
  • String manipulation using pointers
  • File input/output and pointers
  • Manipulating multidimensional arrays
  • Callback design using function pointers

By understanding pointers, you will be able to experience the true fun and power of C language. You may feel confused at first, but let’s build your understanding steadily step by step. I hope this article serves as a helpful guide.

侍エンジニア塾