C Pointer Variables: Beginner‑Intermediate Guide with Code

目次

1. Introduction

Understanding pointer variables is when learning C. Beginners may find concepts like “address” and “indirect reference” confusing, but pointers are a fundamental element of C, and mastering them enables more advanced programming. In this article, we will start from the basics of “what is a pointer variable?” and gradually and carefully explain practical code examples, as well as the relationship with arrays and functions and advanced usage. To make the meaning of technical terms and the mental model of how they work easy to grasp, we will include diagrams and sample code, so even readers who are not yet comfortable with C can read with confidence. Pointers become a very powerful and convenient feature once you get used to them. They expand the scope of programs through dynamic memory manipulation and passing values to functions. Through this article, we hope you deepen your understanding of pointer variables and take your C development skills to the next level.

2. What Is a Pointer Variable?

Basic Concept of Pointer Variables

In C, a pointer variable is a variable that stores a memory address. Whereas regular variables store the data itself, a pointer stores the location (address) where another variable is stored. For example, consider the following code.
int a = 10;
int *p = &a;
In this case, variable a holds the value 10, and p holds the address of a. By writing *p, you can indirectly refer to the value at the address pointed to by p (which is 10 in this case).

Relationship Between Addresses and Memory

All data is stored in a computer’s memory. Memory is addressed byte by byte. A pointer uses this address information as a means to specify which memory location to operate on. Using pointers enables the following:
  • Directly modify variable contents across functions
  • Flexibly manipulate array elements
  • Dynamic memory management using heap memory
In other words, pointers are a crucial mechanism that underpins C’s flexibility and low‑level control.

Declaration and Initialization of Pointer Variables

Pointer variables are declared by appending an asterisk (*) to the target data type.
int *p;   // pointer to an int variable
char *c;  // pointer to a char variable
Typically, you assign the address of another variable using the & operator.
int a = 5;
int *p = &a;  // store the address of a in p
The important point here is that the “type of the pointer” and the “type of the value the pointer points to” must match. Storing the address of a char in a pointer that expects an int may lead to undefined behavior.
侍エンジニア塾

3. Basic Pointer Operations

Once you understand the basics of pointer variables, let’s look at exactly how to use them. Here we introduce the essential operators for pointer manipulation, reading and writing values, and basic operations 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 variable a is stored in p. Thus, p holds the location where a is stored.

Indirection Operator (*)

* is called the “indirection operator” or “dereference operator” and is used when a pointer references or modifies the contents at the address it points to.
int a = 10;
int *p = &a;

printf("%d
", *p);  // Result: 10
In this way, writing *p allows you to indirectly obtain the value (contents) of a. Conversely, writing as shown below lets you modify the value.
*p = 20;
printf("%d
", a);  // Result: 20

Getting and Modifying Values: Pointer Usage Example

By using pointers, you can directly change a variable’s value from another function. Below is a basic example. pre>void updateValue(int *p) { *p = 100; } int main() { int num = 50; updateValue(&num); printf(“%d “, num); // Result: 100 return 0; } As shown, pointers are also useful when updating values inside functions. In C, passing a value to a function typically creates a copy (pass‑by‑value), but using a pointer allows you to operate directly on the original value.

Pointer Addition and Subtraction

Pointers support addition and subtraction, which is extremely handy when working with arrays or contiguous memory regions.
int arr[3] = {10, 20, 30};
int *p = arr;

printf("%d
", *p);     // 10
p++;
printf("%d
", *p);     // 20
The important point here is that p++ moves the pointer to the address of the next int variable. If an int is 4 bytes, p++ effectively adds 4 to the address. Understanding these basic pointer operations is the first step in building a foundation for memory manipulation in C. In the next chapter, we’ll explore the relationship between pointers and arrays in more detail.

4. Relationship Between Arrays and Pointers

In the C language, arrays and pointers have a very close relationship. It can be a confusing point for beginners, but understanding this relationship enables more flexible and efficient array manipulation.

Array Names Can Be Treated Like Pointers

In C, an array name is treated as a pointer that points to the address of the first element. For example, see the following code.
int arr[3] = {10, 20, 30};
printf("%d
", *arr);     // Result: 10
At this point, arr points to the same address as &arr[0], and *arr refers to the first element of the array (arr[0]).

Accessing Arrays Using Pointers

Arrays can be accessed by index, but the same operations are possible using pointers.
int arr[3] = {10, 20, 30};
int *p = arr;

printf("%d
", 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 as follows.
printf("%d
", *(arr + 2));   // Result: 30
Thus, index notation and pointer arithmetic essentially do the same thing.

Differences Between Pointer Arithmetic and Array Indexing

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

1. Obtaining Size

When you use an array name, you can obtain its size with sizeof, but once it is assigned to a pointer, the size information is lost.
int arr[5];
int *p = arr;

printf("%zu
", sizeof(arr)); // Result: 20 (5×4 bytes)
printf("%zu
", sizeof(p));   // Result: 8 (in a 64‑bit environment, a pointer is 8 bytes)

2. Ability to Reassign

An array name behaves like a constant pointer, and it 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 manipulate memory space flexibly
  • Array processing becomes faster and more efficient (pointer arithmetic can be slightly faster than indexing in some cases)
  • When passing an array to a function, only the address of the first element is passed, not the actual array, so knowledge of pointers is essential

5. Functions and Pointers

In C, passing variables to functions uses pass-by-value (call by value) by default. Therefore, modifying an argument inside a function does not affect the original variable. However, using pointers allows a function to directly manipulate the original variable’s value. This chapter explains the relationship between functions and pointers, how to rewrite values, the basics of function pointers, and how to use pointers in functions.

Rewriting Values Using Pointers

First, if you want to change a variable’s value from within a function, you need to use a pointer.

Example: Without Pointers

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

int main() {
    int a = 10;
    update(a);
    printf("%d
", 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 Pointers

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

int main() {
    int a = 10;
    update(&a);
    printf("%d
", a); // Result: 100 (changed)
    return 0;
}
Thus, by passing a variable’s address to a function, you can modify the original value. This technique, known as “pass-by-reference (call by reference)”, is widely used.

Arrays and Functions Relationship

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, allowing its contents to 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
", nums[1]); // Result: 10
    return 0;
}
As shown in this example, you can modify the contents from within a function simply by passing the array name.

Basics of Function Pointers

In C, you can also store a function’s address in a variable and call it. This is a function pointer.

Declaration and Example:

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

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

    int result = funcPtr(3, 4);
    printf("%d
", result); // Result: 7
    return 0;
}
Function pointers are used when you want to dynamically select and execute a function, or implement callback functions. Since you can obtain a function’s address simply by using its name, they enable flexible programming.

Practical Use Cases

  • Array sorting by passing a comparison function as a pointer
  • Menu-driven programs where each option’s function is managed with a function pointer
  • Event-driven processing and callback functions implementation (GUI, embedded, game development, etc.)

6. Pointer Application Examples

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

Dynamic Memory Allocation (malloc and free)

In C, you can dynamically allocate memory as needed at runtime. This is made possible by the standard library function malloc. The allocated memory is accessed via a pointer, and you must release it with free when done.

Example: Dynamically Allocating an Integer

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

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

    *p = 123;
    printf("%d\n", *p);  // result: 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
  • When finished using memory, always release it with free
In this way, the ability to allocate exactly the amount of memory you need, when you need it is the appeal of dynamic memory management using pointers.

Pointer to Pointer (Double Pointer)

A pointer that holds the address of another pointer, i.e., a pointer to a pointer, is also commonly used in C. It is especially useful when you need to modify a pointer inside a function or work with 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);  // result: 42
    free(p);
    return 0;
}

Common Use Cases:

  • Array of arrays (2D arrays)
  • When a struct contains a variable-length array
  • Handling multiple strings (e.g., char *argv)

Combining Function Pointers with Arrays

Storing function pointers in an array and dynamically selecting and invoking a function based on the situation is also a common advanced technique in C.

Example: Simple Menu Selection

#include <stdio.h>

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

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;
}
Such a design is also useful for state management and event-driven programming. Advanced pointer techniques require more than just coding ability; they demand design skills that consider memory and execution control. However, once mastered, they enable you to fully leverage the power of C.

7. Common Errors and Their Solutions

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

Using Uninitialized Pointers

The most basic yet dangerous case is using an uninitialized pointer. A pointer does not point to a valid address merely by being declared.

Bad Example:

int *p;
*p = 10;  // Undefined behavior! p does not point anywhere

Solution:

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

Dereferencing a NULL Pointer

If a pointer points to NULL and you dereference it with *p, the program will 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 Leak

If you forget to free dynamically allocated memory, a “memory leak” occurs where memory is not released and accumulates. This is fatal for long-running programs or embedded systems.

Example:

int *p = (int *)malloc(sizeof(int));
 // Not freeing after processing → memory leak

Solution:

  • Always free after use
  • Be aware of matching malloc and free
  • Use memory leak detection tools (e.g., Valgrind) during development

Dangling Pointer

A pointer that points to memory after it has been freed is called a “dangling pointer”, and reusing it causes undefined behavior.

Example:

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

Solution:

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

Out-of-Bounds Array Access

When performing index arithmetic with pointers, you may unintentionally exceed the array bounds, which is also very dangerous and can cause bugs and vulnerabilities.

Example:

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

Solution:

  • Always verify that you are accessing within a valid range
  • Enforce “boundary checks” in loops

Double-Freeing the Same Pointer

If you call free on the same memory address twice, the program may crash.

Solution:

  • By setting the pointer to NULL after free, you can prevent double free
free(p);
p = NULL;
These errors can be prevented by following the basics and coding carefully. Especially for beginners, adhering to the rules of “initialization”, “NULL checks”, and “consistent use of free” leads to bug‑free code.

8. Summary

In C, pointer variables are a fundamental yet deeply important element. This article has progressively covered everything from the basics of “What is a pointer?” to applications such as arrays, functions, memory management, and function pointers.

Recap of Learned Points

  • Pointer variables are variables that store the address of data and are manipulated using * (indirection operator) and & (address operator).
  • Arrays and pointers are closely related, and an array name can be treated as a pointer to its first element.
  • Functions and pointers combined enable “pass-by-reference” style direct manipulation of variables within functions, and using function pointers allows flexible function calls.
  • Techniques such as dynamic memory management (malloc/free) and double pointers support more practical and flexible program design.
  • On the other hand, pointer-specific errors such as uninitialized pointers, NULL dereferences, memory leaks, and dangling pointers are common, requiring careful handling.

Advice for Beginners

Pointers often get a reputation for being “hard” or “scary,” but that’s because they’re used as a black box. By thoroughly understanding what addresses mean and how memory works, anxiety turns into confidence. Following these steps can help solidify your learning:
  • Trace sample code by hand on paper and diagrams
  • Use printf to visualize and verify addresses and values
  • Leverage Valgrind and other memory-checking tools
  • Write several small pointer manipulation practice programs

Next Steps

The content covered in this article spans from basic to intermediate topics on C pointers. To deepen your understanding further, consider moving on to the following topics.
  • Structures and pointers
  • String manipulation using pointers
  • File I/O and pointers
  • Multidimensional array operations
  • Callback design using function pointers
Understanding pointers lets you experience the true power and elegance of C. You may feel confused at first, but build your understanding step by step. I hope this article helps you on that journey.