How to Work with Pointers in C Programming
Tags: programming, c, backend, tutorial
Published: 2023-03-30 19:42:41

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:

```C
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`:

```C
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:

```C
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:

```C
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:

```C
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. 

:white_check_mark: 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:

```C
char str[] = "hello";
```
Or alternatively,
```C
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:

```C
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:

```C
printf("%d", *(ptr + 1));
```
This will print the value 2, which is the second element in the array.

:gem: **Bonus question**: how to print the fourth element? Here is the solution:
```C
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:

```C
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:

```C
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 :blush:)


### 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:

```C
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`:

```C
int num = 9;
int *ptr = &num;
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:

```C
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`:

```C
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:

```C
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:

```C
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:

```C
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:

```C
int third = *(ptr + 2);
```
This is equivalent to:
```C
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:

```C
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:

```C
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:

```C
int num = 9;
int *ptr = &num;
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:

```C
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.