Recent Hot Articles
2025 Latest C Language Learning Path | Beginner, Intermediate, PracticalC Language Learning Guide: Have You Mastered These Core Knowledge Points?C Language Functions: From Beginner to Mastery – A Comprehensive GuideBeginner’s Guide to Avoiding Pitfalls in C Language: Avoid These 12 Devilish Details for Double Efficiency!C Language Examples | Creating, Inserting, and Traversing Linked Lists
Table of Contents
- Basic Concepts of Pointers
- Memory Model of Pointers
- Pointer Types and Declarations
- Basic Operations on Pointers
- Relationship Between Pointers and Arrays
- Pointers and Strings
- Pointers and Functions
- Pointer Arithmetic
- Multi-level Pointers
- Pointer Arrays and Array Pointers
- Function Pointers
- Dynamic Memory Allocation
- Advanced Applications of Pointers
- Common Errors and Traps
- Best Practices for Using Pointers
- Case Studies
Before the main content, I recommend 3 books for learning C language
Main Content
Basic Concepts of Pointers
A pointer is one of the most powerful and complex features in C language. In simple terms, a pointer is a variable whose value is the memory address of another variable. Through pointers, we can indirectly access and manipulate data stored at specific memory locations.
Why Do We Need Pointers?
- Direct Memory Operations: Allows programs to directly access and manipulate memory
- Efficient Data Passing: Avoids copying large amounts of data
- Dynamic Memory Management: Allocates and frees memory at runtime
- Implementing Complex Data Structures: Such as linked lists, trees, graphs, etc.
- Supporting Callback Mechanisms: Implemented through function pointers
Basic Terminology of Pointers
- Pointer Variable: A variable that stores a memory address
- Object Pointed to by the Pointer: The data stored at the memory location corresponding to the address in the pointer variable
- Dereferencing: Accessing the object pointed to by the pointer
- Pointer Type: Determines the size of memory accessed and the interpretation method during dereferencing
Memory Model of Pointers
To understand pointers, one must first understand the basic principles of computer memory.
Memory Model
Computer memory can be imagined as a series of consecutively numbered bytes (each byte is 8 bits). Each byte has a unique address, starting from 0 and incrementing.
Address: 0x1000 0x1001 0x1002 0x1003 0x1004 ...
Content: [0x42] [0x65] [0x6C] [0x6C] [0x6F] ...
Representation of Variables in Memory
Assuming there is an integer variable:
int num = 12345; // Assume at memory address 0x1000
Representation in memory (assuming int is 4 bytes):
Address: 0x1000 0x1001 0x1002 0x1003
Content: [0x39] [0x30] [0x00] [0x00] // Binary representation of 12345, considering little-endian
Representation of Pointers in Memory
Pointer variables also occupy memory space, storing the address value:
int num = 12345; // Address: 0x1000
int *p = # // Assume p's address is 0x2000
Memory structure:
Address: 0x1000 0x1001 0x1002 0x1003
Content: [0x39] [0x30] [0x00] [0x00] // Value of num
Address: 0x2000 0x2001 0x2002 0x2003 0x2004 0x2005 0x2006 0x2007
Content: [0x00] [0x10] [0x00] [0x00] [0x00] [0x00] [0x00] [0x00] // Value of p (0x1000)
Pointer Types and Declarations
Pointer Types
The type of a pointer determines:
- The data type pointed to by the pointer
- The size of memory accessed during pointer dereferencing
- The step size for pointer arithmetic
Pointer Declaration Syntax
Type *pointerName;
For example:
int *p; // Pointer to an integer
char *str; // Pointer to a character
double *dp; // Pointer to a double
void *vp; // Void pointer, can point to any type
Variations in Pointer Declarations
int *p1, *p2, *p3; // Three pointers to integers
int* p1, p2, p3; // One pointer to integer p1, two integer variables p2 and p3
int * p1; // Equivalent to int *p1;
Special Nature of Void Pointers
A void pointer is a “generic” pointer that can point to any type of data, but must be typecast before dereferencing:
void *vp = #
int *ip = (int*)vp; // Explicit conversion needed in C
printf("%d", *ip); // Correct
printf("%d", *vp); // Incorrect, cannot directly dereference void pointer
Basic Operations on Pointers
1. Address-of Operation (&)
int num = 10;
int *p = # // p now holds the address of num
2. Dereference Operation (*)
int num = 10;
int *p = #
*p = 20; // Modify the value of num through p
printf("%d", num); // Outputs 20
3. Pointer Assignment
int x = 10, y = 20;
int *p1 = &x;
int *p2 = &y;
p1 = p2; // p1 now points to y
4. Pointer Comparison
if (p1 == p2) { // Compare if two pointers point to the same memory location
printf("Pointing to the same address");
}
if (*p1 == *p2) { // Compare if the values pointed to by two pointers are equal
printf("Pointing to the same value");
}
5. Null Pointer
int *p = NULL; // NULL is usually defined as (void*)0
if (p == NULL) {
printf("This is a null pointer");
}
Relationship Between Pointers and Arrays
In C language, the array name is actually a constant pointer to the first element of the array.
Array Name as Pointer
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // Equivalent to p = &arr[0]
printf("%d", *p); // Outputs 10
printf("%d", *(p+1)); // Outputs 20
Pointer Accessing Array Elements
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
// The following expressions are equivalent
printf("%d", arr[2]);
printf("%d", *(arr+2));
printf("%d", *(p+2));
printf("%d", p[2]);
Differences Between Array Names and Pointers
Although array names can be used as pointers, they have key differences:
- Array names are constant pointers, they cannot be modified:
arr++; // Error, array names cannot be modified
p++; // Correct, p is a variable
- Behavior of sizeof operator is different:
sizeof(arr); // Returns the size of the entire array: 5 * sizeof(int)
sizeof(p); // Only returns the size of the pointer itself, usually 4 or 8 bytes
Multi-dimensional Arrays and Pointers
int matrix[3][4]; // 3 rows and 4 columns of a 2D array
// Accessing elements
matrix[1][2] = 10;
// Using pointers to access
*(*(matrix+1)+2) = 10; // Equivalent to the above statement
// Pointer to a row
int (*row)[4] = matrix; // Pointer to an array of 4 int elements
Pointers and Strings
In C, strings are character arrays terminated by a null character (‘\0’). Strings have a close relationship with pointers.
String Declaration and Initialization
char str1[] = "Hello"; // Character array
char *str2 = "World"; // Character pointer, pointing to a constant string
char *str3 = (char*)malloc(10); // Dynamically allocated string
strcpy(str3, "Dynamic");
String Operations
// String copy
char dest[20];
char *src = "Source";
strcpy(dest, src);
// String concatenation
strcat(dest, " String");
// String length
int len = strlen(dest);
// String comparison
if (strcmp(str1, str2) == 0) {
printf("Equal");
}
String and Pointer Operations
char str[] = "Hello";
char *p = str;
// Traversing the string
while (*p != '\0') {
printf("%c", *p);
p++;
}
// String copy
char *src = "Source";
char *dest = (char*)malloc(strlen(src) + 1);
char *p_dest = dest;
while (*src) {
*p_dest++ = *src++;
}
*p_dest = '\0';
Pointers and Functions
The combination of pointers and functions is one of the most powerful features in C language.
Pointers as Function Parameters
// Modify variable values through pointers
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
swap(&x, &y);
// Now x=10, y=5
return 0;
}
Pointers as Function Return Values
int* findMax(int arr[], int size) {
int *max_ptr = &arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > *max_ptr) {
max_ptr = &arr[i];
}
}
return max_ptr;
}
int main() {
int numbers[] = {5, 12, 7, 9, 3};
int *max = findMax(numbers, 5);
printf("Max value: %d\n", *max);
return 0;
}
Precautions
Returning a pointer to a local variable is dangerous:
int* createInt() {
int x = 10;
return &x; // Dangerous! x will no longer be valid after the function returns
}
The correct approach is to use dynamic memory allocation:
int* createInt() {
int *p = (int*)malloc(sizeof(int));
*p = 10;
return p; // Return pointer to heap memory, caller is responsible for freeing
}
Pointer Arithmetic
Pointers can perform addition and subtraction, but the unit of increment or decrement is the size of the pointer type.
Pointer Addition and Subtraction
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p points to arr[0]
p = p + 1; // p points to arr[1]
p += 2; // p points to arr[3]
p--; // p points to arr[2]
Pointer Subtraction
The result of subtracting two pointers is the number of elements between them:
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[1];
int *p2 = &arr[4];
int diff = p2 - p1; // diff = 3, indicating there are 3 elements from p1 to p2
Different Pointer Types Step Size
char *cp = (char*)0x1000;
int *ip = (int*)0x1000;
cp++; // Now cp = 0x1001 (increases by 1 byte)
ip++; // Now ip = 0x1004 (increases by 4 bytes, assuming int is 4 bytes)
Multi-level Pointers
Pointers can point to another pointer, forming a multi-level pointer structure.
Double Pointers
int num = 10;
int *p = # // Single pointer
int **pp = &p; // Double pointer
printf("%d", **pp); // Outputs 10
Memory representation:
num: [10] Address: 0x1000
p: [0x1000] Address: 0x2000
pp: [0x2000] Address: 0x3000
Applications of Multi-level Pointers
- Dynamic allocation of 2D arrays:
int **matrix = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
- Modifying pointers through functions:
void allocateBuffer(char **buffer, int size) {
*buffer = (char*)malloc(size);
}
int main() {
char *myBuffer = NULL;
allocateBuffer(&myBuffer, 100);
strcpy(myBuffer, "Hello");
free(myBuffer);
return 0;
}
Three-level and Higher Pointers
int ***ppp; // Three-level pointer
int ****pppp; // Four-level pointer
Although theoretically any level of pointer can be used, in practice, pointers of three levels or more are rarely used because the structure becomes difficult to understand and maintain.
Pointer Arrays and Array Pointers
Pointer Arrays
A pointer array is an array where each element is a pointer:
int *arr[5]; // Array containing 5 int pointers
int a=1, b=2, c=3, d=4, e=5;
arr[0] = &a;
arr[1] = &b;
// ...
printf("%d", *arr[1]); // Outputs 2
Pointer arrays are commonly used to manage strings:
char *names[] = {"John", "Alice", "Bob", "Carol"};
printf("%s", names[1]); // Outputs "Alice"
Array Pointers
An array pointer is a pointer that points to an array:
int (*p)[5]; // Pointer to an array containing 5 int elements
int arr[5] = {1, 2, 3, 4, 5};
p = &arr; // p points to the entire array
printf("%d", (*p)[2]); // Outputs 3
Distinguishing Between Pointer Arrays and Array Pointers
<span>int *arr[5]</span>
– Pointer array: An array with 5 elements, each element is an int pointer<span>int (*arr)[5]</span>
– Array pointer: A pointer that points to an array with 5 int elements
Multi-dimensional Arrays and Array Pointers
int matrix[3][4];
// Using array pointer to access
int (*p)[4] = matrix; // p is a pointer to an array of 4 int elements
printf("%d", p[1][2]); // Accesses matrix[1][2]
Function Pointers
A function pointer is a pointer variable that points to a function, which can be used to call functions or pass as parameters.
Defining Function Pointers
// Declare a function pointer type
typedef int (*Operation)(int, int);
// Define functions
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// Using function pointers
Operation op = add;
int result = op(5, 3); // Calls add function, result is 8
op = subtract;
result = op(5, 3); // Calls subtract function, result is 2
Function Pointers as Parameters
int processNumbers(int a, int b, int (*func)(int, int)) {
return func(a, b);
}
int main() {
int result1 = processNumbers(5, 3, add); // Result is 8
int result2 = processNumbers(5, 3, subtract); // Result is 2
return 0;
}
Function Pointer Arrays
int (*operations[4])(int, int) = {add, subtract, multiply, divide};
int result = operations[0](5, 3); // Calls add function
Callback Functions
Function pointers are often used to implement callback mechanisms:
// Sorting function
void bubbleSort(int arr[], int n, int (*compare)(int, int)) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (compare(arr[j], arr[j+1]) > 0) {
// Swap elements
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
// Comparison functions
int ascending(int a, int b) { return a - b; }
int descending(int a, int b) { return b - a; }
// Using
int arr[] = {5, 2, 8, 1, 9};
bubbleSort(arr, 5, ascending); // Ascending sort
bubbleSort(arr, 5, descending); // Descending sort
Dynamic Memory Allocation
C language provides a set of functions for dynamic memory management, which are closely related to pointers.
malloc
Allocates memory of specified byte size:
int *p = (int*)malloc(5 * sizeof(int)); // Allocates space for 5 ints
if (p != NULL) {
for (int i = 0; i < 5; i++) {
p[i] = i + 1;
}
}
calloc
Allocates a specified number of elements and initializes them to 0:
int *p = (int*)calloc(5, sizeof(int)); // Allocates space for 5 ints and initializes to 0
realloc
Resizes allocated memory:
int *p = (int*)malloc(5 * sizeof(int));
// ...use p...
// Extend memory to 10 ints
p = (int*)realloc(p, 10 * sizeof(int));
free
Frees dynamically allocated memory:
free(p); // Free memory
p = NULL; // Avoid dangling pointer
Memory Leaks
If memory is allocated but not freed, a memory leak occurs:
void leakMemory() {
int *p = (int*)malloc(sizeof(int));
*p = 10;
// No call to free(p) at the end of the function, leading to memory leak
}
Dynamic 2D Arrays
// Allocate a 2D array with 3 rows and 4 columns
int **matrix = (int**)malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++) {
matrix[i] = (int*)malloc(4 * sizeof(int));
}
// Use
matrix[1][2] = 10;
// Free
for (int i = 0; i < 3; i++) {
free(matrix[i]);
}
free(matrix);
Advanced Applications of Pointers
Implementing Linked Lists
typedef struct Node {
int data;
struct Node *next;
} Node;
// Create a node
Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Append node to the end of the linked list
void appendNode(Node **head, int data) {
Node *newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
Node *current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
// Print linked list
void printList(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// Free linked list
void freeList(Node *head) {
Node *current = head;
Node *next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
}
Implementing Binary Trees
typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// Create a node
TreeNode* createNode(int data) {
TreeNode *newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// Insert node
TreeNode* insertNode(TreeNode *root, int data) {
if (root == NULL) {
return createNode(data);
}
if (data < root->data) {
root->left = insertNode(root->left, data);
} else {
root->right = insertNode(root->right, data);
}
return root;
}
// In-order traversal
void inorderTraversal(TreeNode *root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}
// Free tree
void freeTree(TreeNode *root) {
if (root != NULL) {
freeTree(root->left);
freeTree(root->right);
free(root);
}
}
Generic Data Structures
Using void pointers to implement generic data structures:
typedef struct GenericNode {
void *data;
struct GenericNode *next;
} GenericNode;
// Create a node
GenericNode* createNode(void *data, size_t dataSize) {
GenericNode *newNode = (GenericNode*)malloc(sizeof(GenericNode));
newNode->data = malloc(dataSize);
memcpy(newNode->data, data, dataSize);
newNode->next = NULL;
return newNode;
}
Common Errors and Traps
1. Uninitialized Pointers
int *p; // Uninitialized, contains random values
*p = 10; // Dangerous! May cause segmentation fault
Correct approach:
int *p = NULL;
if (p != NULL) {
*p = 10; // Safe check
}
// Or
int num;
int *p = #
*p = 10; // Safe
2. Memory Leaks
void memoryLeak() {
int *p = (int*)malloc(sizeof(int));
// Did not call free(p)
}
Correct approach:
void noLeak() {
int *p = (int*)malloc(sizeof(int));
// Use p
free(p);
p = NULL; // Avoid dangling pointer
}
3. Using Freed Memory
int *p = (int*)malloc(sizeof(int));
free(p);
*p = 10; // Dangerous! p has become a wild pointer
4. Pointer Out-of-Bounds Access
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
*(p+10) = 100; // Dangerous! Exceeds array bounds
5. Returning Address of Local Variable
int* badFunction() {
int x = 10;
return &x; // Dangerous! x will no longer be valid after the function ends
}
Correct approach:
int* goodFunction() {
int *p = (int*)malloc(sizeof(int));
*p = 10;
return p; // Return pointer to heap memory, caller is responsible for freeing
}
6. Pointer Type Errors
int num = 10;
char *cp = (char*)#
*cp = 'A'; // Only modifies the first byte of num
7. Modifying String Constants
char *str = "Hello";
str[0] = 'h'; // Dangerous! Attempting to modify string constant
Correct approach:
char str[] = "Hello";
str[0] = 'h'; // Safe, str is an array
Best Practices for Using Pointers
1. Always Initialize Pointers
int *p = NULL; // Initialize to NULL
int num = 10;
int *q = # // Initialize to a valid address
2. Check Results of Dynamic Memory Allocation
int *p = (int*)malloc(size);
if (p == NULL) {
// Handle memory allocation failure
fprintf(stderr, "Memory allocation failed\n");
return -1;
}
3. Free Dynamic Memory After Use
p = (int*)malloc(size);
// Use p
free(p);
p = NULL; // Avoid dangling pointer
4. Avoid Out-of-Bounds Access Caused by Pointer Arithmetic
for (int i = 0; i < size; i++) {
// Instead of directly p++
process(p + i);
}
5. Use const Qualifiers to Protect Data
void printString(const char *str) {
// Content pointed to by str cannot be modified
printf("%s", str);
}
6. Avoid Overusing Pointers
If simple variables can solve the problem, do not use pointers:
// Bad practice
void addOne(int *num) {
(*num)++;
}
// For simple calculations, you can write this
int addOne(int num) {
return num + 1;
}
7. Use typedef to Simplify Pointer Definitions
typedef int* IntPtr;
IntPtr p1, p2; // Two pointers to int
Case Studies
Case 1: String Processing Function
Implement a function to remove all spaces from a string:
char* removeSpaces(const char *str) {
if (str == NULL) return NULL;
int len = strlen(str);
char *result = (char*)malloc(len + 1);
if (result == NULL) return NULL;
int j = 0;
for (int i = 0; i < len; i++) {
if (str[i] != ' ') {
result[j++] = str[i];
}
}
result[j] = '\0';
return result;
}
// Using
char *original = "Hello World!";
char *processed = removeSpaces(original);
printf("%s\n", processed); // Outputs "HelloWorld!"
free(processed);
Case 2: Implementing a Simple Memory Pool
#include <stdio.h>
#include <stdlib.h>
// Define memory block structure
typedef struct MemoryBlock {
struct MemoryBlock *next;
} MemoryBlock;
// Define memory pool structure
typedef struct MemoryPool {
MemoryBlock *freeList;
size_t blockSize;
size_t blockCount;
void *pool;
} MemoryPool;
// Initialize memory pool
void initMemoryPool(MemoryPool *pool, size_t blockSize, size_t blockCount) {
pool->blockSize = blockSize;
pool->blockCount = blockCount;
// Allocate a large block of memory
pool->pool = malloc(blockSize * blockCount);
if (pool->pool == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
// Initialize free list
pool->freeList = (MemoryBlock *)pool->pool;
MemoryBlock *current = pool->freeList;
for (size_t i = 0; i < blockCount - 1; i++) {
current->next = (MemoryBlock *)((char *)current + blockSize);
current = current->next;
}
current->next = NULL;
}
// Allocate memory from the pool
void *allocateFromPool(MemoryPool *pool) {
if (pool->freeList == NULL) {
fprintf(stderr, "Memory pool is full\n");
return NULL;
}
MemoryBlock *allocated = pool->freeList;
pool->freeList = pool->freeList->next;
return (void *)allocated;
}
// Free memory block back to the pool
void freeToPool(MemoryPool *pool, void *block) {
MemoryBlock *released = (MemoryBlock *)block;
released->next = pool->freeList;
pool->freeList = released;
}
// Destroy memory pool
void destroyMemoryPool(MemoryPool *pool) {
free(pool->pool);
pool->freeList = NULL;
pool->blockSize = 0;
pool->blockCount = 0;
pool->pool = NULL;
}
int main() {
MemoryPool pool;
size_t blockSize = 1024;
size_t blockCount = 10;
// Initialize memory pool
initMemoryPool(&pool, blockSize, blockCount);
// Allocate memory
void *block1 = allocateFromPool(&pool);
void *block2 = allocateFromPool(&pool);
if (block1 != NULL && block2 != NULL) {
printf("Allocated two blocks from memory pool\n");
}
// Free memory
freeToPool(&pool, block1);
freeToPool(&pool, block2);
printf("Freed two blocks back to memory pool\n");
// Destroy memory pool
destroyMemoryPool(&pool);
return 0;
}
This memory pool is used to manage fixed-size memory blocks. The basic idea is to pre-allocate a large block of memory and then split it into multiple fixed-size memory blocks. Each time memory is allocated, one available memory block is taken from the free list, and when freed, the memory block is returned to the free list.
Code Explanation:
<span>MemoryBlock</span>
structure: Used to represent a memory block, where the<span>next</span>
pointer is used to build the free list.<span>MemoryPool</span>
structure: Represents the memory pool, containing the free list pointer<span>freeList</span>
, the size of each memory block<span>blockSize</span>
, the number of memory blocks<span>blockCount</span>
, and a pointer to the large block of memory<span>pool</span>
.<span>initMemoryPool</span>
function: Initializes the memory pool, allocates a large block of memory, and initializes the free list.<span>allocateFromPool</span>
function: Allocates a memory block from the memory pool, outputs an error message if the free list is empty.<span>freeToPool</span>
function: Releases a memory block back to the memory pool, inserting it at the head of the free list.<span>destroyMemoryPool</span>
function: Destroys the memory pool, freeing the large block of memory and resetting related pointers and counters.<span>main</span>
function: Demonstrates how to use the memory pool for memory allocation and deallocation.
If you find this article helpful, please like and share it with more like-minded people, thank you!