Back to: C Tutorials For Beginners and Professionals
Pointers in C Language with Examples
In this article, I will discuss Pointers in C Language with Examples. Please read our previous articles, where we discussed Strings in C. As part of this article, you will learn the following concepts in detail.
- What are the Pointers?
- Why do we need to access data indirectly?
- How to access the heap area by the program?
- Where do we need Pointers?
- How to declare a pointer, how to initialize it, and how to use it?
- How do you access heap memory using a pointer?
- How do you get memory in the heap?
- Operators used in Pointer
- What are the Advantages of using Pointers in C Langauge?
- Disadvantages of using Pointers in C Langauge.
What are the Pointers?
The pointer is an address variable meant for storing the data address, not the data itself. Normal variables are data variables, but pointers are address variables and are used for indirectly accessing the data. So, a pointer is a variable that stores the memory address of another variable. Since it points to the location of a variable, it can directly access and manipulate the value stored at that memory address.
Why do we need to Access Data Indirectly?
Let us first understand why we need pointers, and then we will see how to declare and use them. Look at the picture below.
As you can see in the above image, the main memory and CPU will execute our programs. As we discussed in our previous articles, the main memory is divided into Code Section, Stack, and Heap. Our program code (main function or other functions) resides in the code section.
The program can directly access the Code and Stack sections of the Main memory. The program will not directly access the heap. So, the heap memory is external to the program, outside the program, and the program doesn’t access it directly.
How to Access the Heap Area by a C Program?
To access the heap area by the program, we need a pointer. So, one of the reasons for using a pointer is to access the heap area. So, the program should have a pointer to itself, and with that pointer, we can access anything in the heap area, such as an array or any other variables. For a better understanding, please have a look at the below image.
Note: Pointer is useful for accessing the resources that are outside of the program, i.e., outside of the code section and heap memory.
Other Scenarios Where We Need Pointers in C Language:
Suppose on the hard disk we have some files. Then, the program cannot access the hard disk files directly because the hard disk is external or files are external to a program. So, for that, it needs a pointer for accessing, and the pointer should be a file type so that it can access that file.
In some scenarios, the program may be accessing a keyboard, the program may be accessing a monitor, or maybe accessing the internet or network connection. All these things are external to a program. All these things can only be accessed with the help of pointers.
So, one major use of a pointer is accessing the resources outside the program. Pointers are used for accessing heap memory, resources, and parameter passing.
- Dynamic Memory Allocation: Pointers are used with functions like malloc() and free() for dynamic memory allocation, which allows the creation of variables whose size is determined at runtime.
- Array Manipulation: Pointers can be used to iterate through arrays efficiently. They provide a more powerful mechanism to manipulate array elements.
- String Handling: Strings (arrays of characters) are commonly manipulated using pointers in C.
- Function Arguments: Pointers are used to pass addresses of variables to functions (pass-by-reference), enabling the functions to modify the actual arguments used in the call.
- Building Complex Data Structures: Pointers are essential in creating complex data structures like linked lists, trees, and graphs.
How to Declare a Pointer, Initialize it, and use it in C Language?
Let’s take an example. Please have a look at the below image.
As you can see in the above image, here we declare a data variable “a” and initialize it with a value of 10. Pointer variables are also called address variables in C and C++ language. Here, *p is a pointer variable. In our example, both a and *p will be created in the Stack area of the Main memory. Then we initialize the pointer variable (p = &a) to address the data variable a. So, the pointer now storing the address of a. * is called dereferencing.
How to Access Heap Memory using a Pointer in C Language?
Whenever we declare any variable, whether a data variable or pointer variable, it will be created inside the Stack frame of the Main memory, so, in the previous example, both the variables are created inside the Stack memory only. Now, the question is how we will get memory in the heap.
Accessing heap memory in C is done through dynamic memory allocation, which is controlled by a set of standard library functions. These functions allow a program to request memory at runtime and to release it when it’s no longer needed. The most commonly used functions for this purpose are malloc(), calloc(), realloc(), and free().
In C language, there is a function called malloc(). When we write malloc(), we will get the memory in the heap. To use the malloc() function, first, we must include the #include<stdlib.h> header file.
While using the malloc() function, we need to specify the size, i.e., how many bytes of memory we want in the heap. For example, we want memory for 5 integers in the heap. Then, we need to specify the size as shown below. Here, the sizeof(int) will give the size of an integer in bytes depending on the compiler and operating system we used. If the compiler takes 2 bytes for an integer, it will allocate 10 bytes in the heap memory. If the compiler takes 4 bytes for an integer, it will allocate 20 bytes in the heap memory.
malloc(5 * sizeof(int));
The malloc function returns a void pointer. So, we need to typecast it to an integer pointer, as shown below.
int *p;
p = (int *) malloc(5 * sizeof(int));
In C++, you can do the same using a new operator, as shown below.
int *p;
p = new int[5];
To better understand the memory architectures, please look at the following image.
Using malloc()
Purpose: Allocates a specified number of bytes of uninitialized memory.
Syntax: void *malloc(size_t size);
Usage:
int *ptr = (int*) malloc(sizeof(int)); // Allocates memory for one integer if (ptr != NULL) { *ptr = 5; // Assign a value to allocated memory }
Error Checking: Always check if malloc() returns NULL, which indicates that memory allocation failed.
Using calloc()
Purpose: Allocates memory for an array of elements of a specified size and initializes all bytes to zero.
Syntax: void *calloc(size_t nitems, size_t size);
Usage:
int *ptr = (int*) calloc(5, sizeof(int)); // Allocates memory for an array of 5 integers if (ptr != NULL) { ptr[0] = 1; // Assign a value to the first element }
Error Checking: Like malloc(), check for a NULL return value.
Resizing Memory with realloc()
Purpose: Change the size of the memory block without losing the existing data.
Syntax: void *realloc(void *ptr, size_t size);
Usage:
ptr = realloc(ptr, 10 * sizeof(int)); // Resizes the previously allocated memory to 10 integers
Error Checking: Check for NULL and ensure the original pointer is not lost.
Releasing Memory With free()
Purpose: Frees the allocated memory, returning it to the heap.
Syntax: void free(void *ptr);
Usage:
free(ptr); ptr = NULL; // Good practice to set the pointer to NULL after freeing
Example: Allocating Memory for an Array in C
#include <stdio.h> #include <stdlib.h> int main() { int n = 5; int *arr = (int*) malloc(n * sizeof(int)); // Allocate memory for an array of 5 integers if (arr == NULL) { printf("Memory allocation failed.\n"); return 1; } // Initialize array for (int i = 0; i < n; i++) { arr[i] = i; } // Use the array for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } free(arr); // Free the allocated memory arr = NULL; // Set the pointer to NULL return 0; }
Key Points:
- Heap vs. Stack: Memory allocated on the heap persists until it’s explicitly freed or the program terminates. This is different from stack memory, which is automatically managed and has a limited size.
- Memory Leaks: Failing to free-heap memory when it’s no longer needed can lead to memory leaks, a common issue in C programming.
- Pointer Type Casting: The functions return a void* type, which is a generic pointer in C. It’s often cast to the appropriate pointer type.
I hope you understand the basics of the pointer. Now let us proceed and understand the concept pinter in-depth with more examples.
What are Pointers in C?
Pointers in C are a fundamental and powerful feature, allowing for direct manipulation and access to memory. They are variables that store the memory addresses of other variables, enabling various dynamic memory techniques and efficient data handling. A pointer is a variable that holds the address of another variable. It “points” to a location in memory where data is stored.
Operators used in Pointer in C Language:
In the C programming language, pointers are manipulated using a set of specific operators. Understanding these operators is crucial for effective pointer handling. We need to use the following two operators when working with the pointer.
- * (Indirection operator or dereference operator or object at location or value at address)
- & (address-of operator)
& (address-of operator):
The & is a unary operator. The address-of-operator always returns the base address of a variable. The starting cell address of any variable is called the base address. Retrieves the memory address of a variable. Example: If int x = 10;, then &x will give the memory address of x.
* (Indirection operator):
The Indirection (*) Operator is a unary operator. It is used to create a pointer variable. Indirection Operator always returns the value of an address, i.e., what address we are passing, and from that address, the corresponding value will be retrieved. Accesses the value at the address pointed to by a pointer. Example: If int *ptr = &x;, then *ptr will give the value 10.
Declaration of Pointer:
Syntax: Datatype *ptr_name;
Example: int *p;
Here, the * mark before the variable indicates that it is a pointer variable. Indirection operators must be required between the datatype and variable name. Space is not mandatory to place in the declaration of the pointer variable.
Initialization of Pointer:
Syntax:
Datatype variable;
Datatype *ptr = &variable;
Example to Understand How to Create a Pointer in C Language:
#include <stdio.h> int main() { int x = 10; int *p = &x; printf("Value of x: %d\n", x); printf("Address of x: %p\n", (void*)p); printf("Value at the address stored in p: %d\n", *p); return 0; }
Types of Pointers in C:
In C programming, pointers are a fundamental concept used for various purposes. There are several types of pointers, each with its specific use case. Understanding these different types is crucial for effective programming in C. Here’s an overview of the main types of pointers in C:
Basic Pointers
- Description: Regular pointers that store the address of a variable of a specific data type.
- Usage: Used for general-purpose referencing and memory manipulation.
Pointer to Pointer (Double Pointer)
- Description: A pointer that stores the address of another pointer. They are declared using two asterisks (**).
- Usage: Often used in dynamic memory allocation and handling multi-dimensional arrays.
Array Pointers
- Description: Pointers that point to an array. They can be used to iterate through an array.
- Usage: Useful for efficient array manipulation and when passing arrays to functions.
Function Pointers
- Description: Pointers that point to a function. They are declared with the syntax of the function they point to.
- Usage: Used for callback functions, implementing function tables, and in event-driven programming.
Void Pointers (Generic Pointers)
- Description: A type of pointer that can hold the address of any data type. They are declared using void *.
- Usage: Useful for implementing generic functions and data structures, as they can store a reference to any type.
Null Pointers
- Description: A pointer that is explicitly set to NULL, meaning it points to nothing.
- Usage: Used for initialization and error checking to ensure a pointer doesn’t unintentionally point to a random memory location.
Wild Pointers
- Description: Uninitialized pointers that point to some arbitrary memory location.
- Usage: Generally avoided and considered a bad practice, as they can lead to unpredictable behavior and bugs.
Dangling Pointers
- Description: Pointers pointing to memory that has been freed or deallocated.
- Usage: These are dangerous and should be avoided. Typically, a pointer is set to NULL after freeing the memory it points to, to prevent dangling.
Near, Far, and Huge Pointers
- Description: These are specific to the segmented memory models in older systems, like in 16-bit architectures.
- Near Pointer: Can access data within the same segment.
- Far and Huge Pointers: Used to access memory outside the current segment.
- Usage: Mainly historical significance; modern systems and compilers generally do not use segmented memory models.
Constant Pointers and Pointers to Constants
- Constant Pointer: A pointer whose address it holds cannot be changed.
- Pointer to Constant: A pointer that points to a constant value; the value it points to cannot be changed through the pointer.
Example:
Here’s a simple example demonstrating a basic pointer and a pointer to a pointer:
#include <stdio.h> int main() { int var = 10; int *ptr = &var; // Basic pointer int **pptr = &ptr; // Pointer to pointer printf("Value of var = %d\n", var); printf("Value available at *ptr = %d\n", *ptr); printf("Value available at **pptr = %d\n", **pptr); return 0; }
What are the Advantages of using Pointers in C Langauge?
Pointers in C provide several significant advantages, making them a powerful feature for various programming tasks. Their efficient handling of memory and data structures, among other benefits, makes them indispensable in many scenarios. Here are some of the key advantages of using pointers in C:
- Dynamic Memory Allocation (Memory Management): Pointers are essential for dynamic memory allocation using functions like malloc(), calloc(), and realloc(). This allows programs to utilize memory as needed at runtime, which is especially useful for handling data whose size is unknown at compile time.
- Direct Access to Array Elements (Efficient Array Manipulation): Pointers provide a more efficient way to access and manipulate array elements. You can use pointer arithmetic to iterate through an array, which is often faster and more memory-efficient than array indices.
- Pass-by-Reference (Function Arguments): Pointers enable the pass-by-reference capability in C, allowing functions to modify the actual data passed to them. Without pointers, C functions can only manipulate data by value, meaning changes made within functions do not affect the original arguments.
- Enabling Data Structures (Complex Data Structures): They are crucial for building complex data structures like linked lists, trees, graphs, etc. Pointers provide a way to create these structures and efficiently manage their nodes and elements.
- Manipulation of Strings (String Handling): C uses character arrays to represent strings, and pointers provide an efficient way to handle and manipulate these strings.
- Sharing Data Across Different Areas of a Program (Memory Sharing): By pointing to the same memory location, different parts of a program can share and manipulate data, reducing the need for duplicate storage.
- Direct Memory Access (System-Level Operations): Pointers allow for direct interaction with memory, which is useful for system-level programming, such as writing device drivers or interacting with hardware.
- Reduced Memory and Execution Time (Efficiency): They can lead to more efficient code, as they often reduce the memory footprint and execution time of programs, particularly in handling large amounts of data or complex structures.
- Dynamic Function Calls (Function Pointers): Function pointers allow for dynamic function calls and the implementation of callback functions, which are essential in event-driven programming and interfaces.
Disadvantages of using Pointers in C Language:
While pointers in C offer powerful memory management and data manipulation capabilities, their use also comes with several disadvantages and potential pitfalls. Understanding these drawbacks is crucial for writing safe and reliable C programs:
- Complexity and Difficulty in Understanding: Pointers can be conceptually difficult to grasp, especially for beginners. The abstract nature of pointers, along with pointer arithmetic and indirection, can lead to confusion and errors.
- Memory Management Issues: Pointers require explicit memory management when used with dynamic memory allocation (e.g., using malloc, calloc, and free). Improper handling can lead to memory leaks (not freeing memory), dangling pointers (pointers that reference freed memory), and memory corruption.
- Security Vulnerabilities: Incorrect use of pointers can lead to security vulnerabilities like buffer overflows, especially when dealing with arrays and strings. Such vulnerabilities are a common target for exploits in software.
- Debugging Difficulty: Debugging issues related to pointers can be challenging. Problems like segmentation faults or memory corruption can be hard to trace back to their source, as the symptoms may not directly point to the actual location of the error in the code.
- Undefined Behavior: Dereferencing uninitialized pointers, null pointers, or pointers that have been freed can lead to undefined behavior, causing the program to crash or produce unpredictable results.
- Portability Issues: The behavior of pointers can depend on the architecture, such as differences in data size (e.g., size of int, long, pointer) and endianess. This can affect the portability of the code across different platforms.
- Pointer Arithmetic Risks: If not done carefully, pointer arithmetic can lead to errors. For example, moving a pointer beyond the allocated memory can cause access violations.
- Reduced Readability and Maintainability: Overusing pointers can make code less readable and maintainable. Excessive indirection and complex pointer expressions can make code hard to understand, increasing the likelihood of errors.
- Allocation and Deallocation Overhead: Dynamic memory allocation and deallocation come with runtime overhead and can lead to fragmentation in long-running programs.
- Type Safety Issues: Pointers can be cast into different types, potentially bypassing the type system and leading to type safety issues.
In the next article, I will discuss Arithmetic Operations on Pointers in the C language. In this article, I try to explain Pointers in C Langauge with Examples. I hope you enjoy the Pointers in C Language article. I would like to have your feedback. Please post your feedback, questions, or comments about this Pointer in C Language with Examples article.