The true power and potential of dealing directly with memory are unlocked by the pointers feature of the C programming language. With pointers, you can design complicated data structures like arrays, strings, and structures that can optimize memory usage because you have direct access to the memory addresses of your data.
In this post, we'll go in-depth on pointers in C programming and look at how they function with different data types. You'll be able to create more effective and robust programs that fully utilize the enormous computational power that C offers by grasping the complexities of pointers.
Basic Usage of Pointers
Pointers with Integers
A pointer is a variable that stores the memory address of another variable. In other words, a pointer points to a specific memory location in the computer's memory. The kind of data that a pointer variable will point to must be specified when the variable is declared. Use the following syntax, for instance, to declare a pointer that points to an integer:
int *ptr;
This declares a pointer variable named ptr
that has the ability to point to an integer value.
The &
operator, which retrieves a variable's memory address, can be used here to fetch the memory address and then assign it to a pointer variable. For instance, you would use the following syntax to provide the pointer variable ptr
the memory address of a variable named a
:
int a = 9;
int *ptr = &a;
The memory address of the variable a
is now stored in ptr
. The value stored at the memory address that the pointer points to can be accessed using the * operator. For instance, you would use the following syntax to display the value of the variable a
using the pointer:
printf("%d", *ptr);
This will print the value 9, which is the value stored in the variable a
.
Pointers with Strings
In C, a string is implemented as a null-terminated characters array. To work with strings using pointers, you can declare a pointer variable that points to the first character in the string. For instance, the syntax as follows would be used to declare a pointer that points to a string literal:
char *str = "hello";
The memory address of the first character, h
, in the string hello
is now stored in str
. To access the characters in the string, just use *
operator. Dereferencing is accomplished via the *
operator. For instance, you could apply the following syntax to print the string's first character:
printf("%c", *str);
This will print the character h
.
Note: In the above example, str is a string literal instead of a string array. They are different because string literals are string constants, which cannot be changed, and are stored in the global's read-only memory.
✅ If you want to modify the characters in the string, you can declare str
as a string array instead of a string literal with code like the following examples:
char str[] = "hello";
Or alternatively,
char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
Pointers with Arrays of Integers
Arrays in C are stored in a contiguous block of memory. You can declare a pointer variable that refers to the array's first element to work with arrays via pointers. For example, the following syntax would be used to declare a pointer that points to an array of integers:
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr;
Now, ptr
contains the memory address of the first element in the array. You can use pointer arithmetic to access the other elements in the array. For example, to print the second element in the array, you would use the following syntax:
printf("%d", *(ptr + 1));
This will print the value 2, which is the second element in the array.
💎 Bonus question: how to print the fourth element? Here is the solution:
printf("%d", *(ptr + 3));
Pointers with Nested Array of Strings
An array that comprises other arrays is referred to as a nested array in C. You can declare a pointer variable that points to the first element in the outer array to work with nested arrays using pointers. For example, the following syntax would be used to declare a pointer that points to a nested array of strings:
char *arr[][2] = {{"hello", "world"}, {"I'm", "Jenny"}, {"from", "UofT"}};
char *(*ptr)[2] = arr;
In this two dimensional array called arr
, it is an array of pointers to characters.
The first set of empty square brackets indicates that arr
is an array with some number of rows. Actually, the number of rows will be inferred by the compiler when you give it an initial value, which is 3
in this case. (Thanks to the compiler, we can use this short-cut to omit the array size.)
The second set of square brackets specifies the number of columns, which is 2
in this case.
After we initialize the array with three rows and two columns, the first row contains the strings "hello"
and "world"
, the second row contains the strings "I'm"
and "Jenny"
, and the third row contains the strings "from"
and "UofT"
, here, each element is a string literal (i.e., char *).
Notice that each row of the array arr
is an array of pointer to char. This is because each row of arr
contains a pointer to a one-dimensional array of length two, a pointer to an array of strings is effectively a pointer to the first string of that array of strings, and a pointer to a string is actually the memory address of that string.
Then, we declare the variable ptr
, which is a pointer to a two-dimensional character array, it is initialized to point to the array arr
. So ptr is an alias of arr
, because they store the same memory address.
The memory address of the first element of the outer array is now stored in ptr. To access the inner arrays and their elements, use pointer arithmetic and dereferencing. For example, you would use this syntax to output the second string in the first inner array:
printf("%s", *(*ptr + 1));
This line of code will print the string "world"
.
Let's look at the expression *(*ptr + 1)
. First of all, *ptr
dereferences ptr
to obtain the two-dimensional array arr
, and then, *ptr + 1
is the memory address of the second element in the first row of the arr
array, and then with *(*ptr + 1)
, we dereference the pointer to get the string literal stored at that address, which is "world"
. (More about pointer arithmetic at the later part of the blog, stay tuned 😊)
Using Pointers as Parameters to Functions
Pointers are a type of parameter that functions in C can accept. This enables the function to make changes to the original variable outside the function. A function must be declared to accept a pointer argument in order to accept a pointer as a parameter. As an illustration, you would use the following syntax to send a pointer to an integer variable to the increment function:
void increment(int *ptr) {
(*ptr)++;
}
This function takes a pointer to an integer as a parameter, and then it increments the value stored at the memory location pointed to by this pointer named ptr
.
By giving a pointer to an integer variable, you can invoke the function. For example, the following syntax could be applied to increase the value of the integer num
:
int num = 9;
int *ptr = #
increment(ptr);
printf("%d", num);
This will print out the value 10
.
Passing Arrays as Parameters to Functions
Arrays can also be passed as parameters to functions in C. In this case, you effectively send a pointer to the array's first element when you pass an array to a function. For example, the following syntax would be used to pass an array of integers to the sum function:
int sum(int *arr, int size) {
int res = 0;
for (int i = 0; i < size; i++) {
res += arr[i];
}
return res;
}
This function accepts an array of integers and the size of the array as inputs and returns the sum of the elements.
You can call the function by passing an array of integers and the size of the array. For example, the following code computes the sum of an array of integers named nums
:
int nums[] = {1, 2, 3, 4, 5};
int size = sizeof(nums) / sizeof(nums[0]);
int result = sum(nums, size);
printf("%d", result);
This will output the value 15
, which is the sum of the values in the nums
array.
Advanced Usage of Pointers
Pointers Arithmetic
As you have seen earlier, you can perform arithmetic operations on pointers thanks to the powerful pointer arithmetic capability of pointers in C. You can access an array's elements using pointer syntax and traverse through them using pointer arithmetic. The size of the data type that the pointer is pointing to determines how pointer arithmetic works in C.
Think about the following code, for instance:
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr;
In this code, ptr
points to the first integer of the arr
array. To access the second integer of the array using pointer arithmetic, you can use the following code:
int second = *(ptr + 1);
Here, ptr
is incremented by one, which moves the pointer to the memory location of the second element in the array. What is actually added to ptr
is 1 * sizeof(int)
, so if an integer takes four bytes in your computer, then actually four bytes are added to the memory address stored at ptr
. By moving ahead 4 * sizeof(int)
, now ptr
is storing the memory address of the second integer. The *
operator is used to dereference the pointer and access the value stored at that memory location.
Note that you can use the array notation to fetch the second element as well:
int second = ptr[1];
Really, by ptr[1]
, what is happening behind the hood is that *(ptr + 1)
is evaluated. You can substitute any other number n
that is in-bound of the size of the array to replace 1
. For example, you can write the following line of code to fetch the third element:
int third = *(ptr + 2);
This is equivalent to:
int third = ptr[2];
You can also use pointer arithmetic to print out each element while iterating through an array. For example, consider the following code:
for (int i = 0; i < 5; i++) {
printf("%d ", *(ptr + i));
}
Using pointer arithmetic, this code iterates through the arr
array and outputs each element.
Pointers to Pointers
In C, pointers can also be used to hold another pointer's memory address. They are also referred to as double pointers or pointers to pointers. When you need to change a pointer variable inside a function or make a dynamic two-dimensional array, pointers to pointers come in handy.
You can use the following syntax to declare a pointer to another pointer:
int **pt_ptr;
This declares the pointer variable pt_ptr
, which can to hold the memory address of another pointer to integer.
Think about the following code, for instance:
int num = 9;
int *ptr = #
int **pt_ptr = &ptr;
Here, pt_ptr
is a pointer that stores the memory address of the pointer variable, which points to the integer num
. You can access the value stored at the memory location pointed to by pt_ptr
using the following code:
int value = **pt_ptr;
Here, the *
operator is used twice to dereference the pointer to access the value stored at the memory location pointed to indirectly by pt_ptr
.
The first time you dereference pt_ptr
with * pt_ptr
, you can get the memory address of num
, the second time you dereference * pt_ptr
by saying ** pt_ptr
, you can now get the value stored in num
, which is 9
, therefore 9
is then assigned to value
, which is a variable of type int
.
Conclusion
The C programming language has a powerful feature called pointers that lets you work with memory directly. We explored pointers in C programming and provided examples of how to use them with strings, integer arrays, nested arrays of strings, and nested arrays of arrays of numbers in this blog post. We also covered how to pass arrays as arguments and how to use pointers as parameters to functions. The use of pointers to pointers and pointer arithmetic was also covered, along with examples. You can create more effective and powerful applications by grasping how pointers operate in C programming.
Top comments (3)
That should be
char const *str = "hello";
Without it, the compiler would have erroneously allowed you to modify the string — which is undefined behavior.A string literal is a string array (of
char
), just a constant one. Also note that any constant array also can not be changed:Hi Paul,
Thank you for your comment. And it is a great suggestion to declare
str
aschar const *str = "hello"
, I really like it, as it is more appropriate since it prevents attempts to modify the string literal which is undefined behaviour.To add on to your comment, I should have said that
str
is a string literal instead of a normal string array. Thank you for pointing this out. It indeed is an array of null terminatedchar
's, the main difference is that a string literal is stored in the read-only memory.It would be better to define constant arrays with the keyword
const
to prevent later attempts to modify it, which would result in undefined behaviour.That is a great comment. Thank you again for your great suggestion :D
str
isn't a string literal. The only things that are string literals are literally strings like"hello"
— that's why they're called string literals. In this case,str
is a pointer that points to a string literal.