Pointers are a fundamental concept in many programming languages, particularly in C++ and C. When you want to create a structure like a tree, graph, or list, using pointers to connect elements is essential. Pointers are also used to handle strings, avoid copying structures and dynamically allocate memory. But if we want to allocate memory on a more specific, individual basis, we can use a kind of structure known as an array of pointers. Today we’re going to explore what an array of pointers in C is, the different types, and how they’re used.
What is an Array of Pointers?
An array is a data structure where elements are stored contiguously, meaning without gaps in the memory. These elements can be one of many data types, such as integers, floats, characters, strings, or even sets of values. However, another way to use arrays is to create an array of pointers. This is where we store pointers as the elements, instead of a data type. These pointers are directed to memory locations, which themselves point to data locations somewhere else.
How are Arrays of Pointers Used?
The general syntax for defining an array of pointers in C is as follows:
ptr_type* name_of_array[size_of_array];
where “ptr_type” is the data type the pointer is referring to, “name_of_array” is the array name, and “size_of_array” is the number of elements in the array.
Allocating Memory for an Array of Pointers
Generally, we need to allocate memory for each pointer before we can use the array. There are two main methods for doing this – either using the malloc() function or assigning a variable address. Let’s cover both of these.
Memory allocation using malloc()
One way to allocate memory is by using malloc() and specifying the desired size. Consider this code for an example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int size = 5;
int** array_of_pointers = malloc(size * sizeof(int*));
if (array_of_pointers == NULL) {
printf("Memory allocation failed.\n");
return 1;
}
for (int i = 0; i < size; i++) {
array_of_pointers[i] = malloc(sizeof(int));
if (array_of_pointers[i] == NULL) {
printf("Memory allocation failed for element %d.\n", i);
return 1;
}
*(array_of_pointers[i]) = i * 10;
}
for (int i = 0; i < size; i++) {
printf("Element %d: %d\n", i, *(array_of_pointers[i]));
}
for (int i = 0; i < size; i++) {
free(array_of_pointers[i]);
}
free(array_of_pointers);
return 0;
}
Here, we define the main “int” function, which initializes the “size” value to 5. We then allocate memory to the array of pointers using “malloc()”, with the memory size being calculated by multiplying “size” by the pointer size. An if statement is then used to check the success.
We then use a for loop to allocate memory for each pointer, and another if statement to check the success. The memory allocated for each element is declared as “i + 10”.
Finally, we print the elements to the console and free the memory allocated using the “free()” function. The results can be seen in the image.

©History-Computer.com
Memory allocation by assigning address
The other method is by assigning the address of a variable to each pointer. The code for this is a little simpler, and can be described as such:
#include <stdio.h>
int main() {
int size = 5;
int* array_of_pointers[size];
for (int i = 0; i < size; i++) {
int value = i * 10;
array_of_pointers[i] = &value;
}
for (int i = 0; i < size; i++) {
printf("Element %d: %d\n", i, *(array_of_pointers[i]));
}
return 0;
}
We still have an array of size 5 here, but we use a for loop slightly differently. The “value” variable is declared, and assigned the value “i + 10”. This is then assigned to each pointer using the “&” operator. Elements are then printed as seen in the image.
It should be noted that the malloc() method is preferable to the address method. This is because we can allocate and deallocate memory dynamically, and have a defined lifetime for memory access. Using addresses doesn’t have a defined lifetime, so we may access memory beyond the scope of our project. When using arrays of pointers, malloc() offers greater flexibility, as well as more compatibility with other compilers.

©History-Computer.com
Accessing Elements in an Array of Pointers
When we want to access the elements in our array, we have to use a combination of indexing and dereferencing. In simple terms, indexing an array means we access elements based on their index, with the first element starting at index 0. Dereferencing refers to retrieving the data that the pointer is pointing to. For example, we have this code block:
#include <stdio.h>
int main() {
int num1 = 10, num2 = 20, num3 = 30;
int* array_of_pointers[3];
array_of_pointers[0] = &num1;
array_of_pointers[1] = &num2;
array_of_pointers[2] = &num3;
for (int i = 0; i < 3; i++) {
int value = *(array_of_pointers[i]);
printf("Element %d: %d\n", i, value);
}
return 0;
}
We begin by declaring 3 integer variables, “num1”, “num2”, and “num3”, which are initialized with 10, 20, and 30 respectively.
We then declare an array of pointers of size 3, and assign the addresses of the integer variables to these pointers. Each element is accessed via indexing, using a for loop.
After this, the “*” operator is used to dereference the pointers, meaning the value they point to is retrieved. These values are assigned to the “value” variable. Each element is printed, along with its index, by using the “%” operator. “’\n” is used to create a new line after each element. You can see the output in the image below.

©History-Computer.com
Using an Array of Pointers with Different Data Types
It’s recommended to use more than data structure when working with different data types. This is because you can run into type safety issues, as the compiler can’t check the data types at compile time. You could potentially point to the wrong data type, or allocate memory to the wrong type, as well as make your code more confusing. However, by using a type of pointer known as a void pointer, we can hold a variety of data types within our array. To demonstrate, we have this code block:
#include <stdio.h>
int main() {
int num = 10;
float f = 3.14;
char c = 'A';
void* array_of_pointers[3];
array_of_pointers[0] = #
array_of_pointers[1] = &f;
array_of_pointers[2] = &c;
int* int_ptr = array_of_pointers[0];
float* float_ptr = array_of_pointers[1];
char* char_ptr = array_of_pointers[2];
printf("Value of integer: %d\n", *int_ptr);
printf("Value of float: %f\n", *float_ptr);
printf("Value of char: %c\n", *char_ptr);
return 0;
}
First, we declare 3 variables of different types – an integer, a float, and a character. These are initialized as 10, 3.14, and c respectively.
We then create the array of pointers, but with “void” type this time, and assign the addresses of the 3 variables to the array elements.
After this, we initialize the pointers, which point to their respective data type. Lastly, we dereference and print the results, making sure to use specific operators that match the pointers with their correct type. The output is in the image below.

©History-Computer.com
Pros, Cons, and Applications of Array of Pointers in C
As with any data structure, there are advantages, drawbacks, and best-use cases. The pros and cons of an array of pointers are given in the table.
Pros | Cons |
---|---|
Can be used to store multiple strings | Can make code more complicated |
Used in sorting algorithms | Potentially more memory-intensive than a typical array |
Can be used with any pointer type | Can run into type safety issues if pointer types aren’t managed correctly |
Allow for dynamic memory allocation and efficient memory usage | Extra levels of indirection can slow performance |
While arrays of pointers can cause issues, they’re still useful in many situations. These include dynamic memory allocation, passing data by reference (avoiding copying structures), handling strings, arithmetic operations, and enforcing polymorphism.
Wrapping Up
To conclude, arrays of pointers in C are a useful way to manage memory, perform operations and provide polymorphic, efficient, and intuitive code. While they do need to be handled properly to avoid memory wastage, type safety issues, and overly complex code, they remain a very flexible method for many programming problems.
The image featured at the top of this post is ©TippaPatt/Shutterstock.com.