Pointers are arguably the most difficult feature of C to understand. But, they are one of the features which make C an excellent language.

In this article, we will go from the very basics of pointers to their usage with arrays, functions, and structure.

So relax, grab a coffee, and get ready to learn all about pointers.

Topics

A. Fundamentals
  1. What exactly are pointers?
  2. Definition and Notation
  3. Some Special Pointers
  4. Pointer Arithmetic
B. Arrays and Strings
  1. Why pointers and arrays?
  2. 1-D Arrays
  3. 2-D Arrays
  4. Strings
  5. Array of Pointers
  6. Pointer to Array
C. Functions
  1. Call by Value v/s Call by Reference
  2. Pointers as Function Arguments
  3. Pointers as Function Return
  4. Pointer to Function
  5. Array Of Pointers to Functions
  6. Pointer to Function as an Argument
D. Structure
  1. Pointer to Structure
  2. Array of Structure
  3. Pointer to Structure as an Argument
E. Pointer to Pointer
F. Conclusion

A. Definition, Notation, Types and Arithmetic

1. What exactly are pointers?

Before we get to the definition of pointers, let us understand what happens when we write the following code:

int digit = 42;

What exactly are pointers?

A block of memory is reserved by the compiler to hold an int value. The name of this block is digit and the value stored in this block is 42.

Now, to remember the block, it is assigned with an address or a location number (say, 24650).

The value of the location number is not important for us, as it is a random value. But, we can access this address using the & (ampersand) or address of operator.

printf("The address of digit = %d.",&digit);
 /* prints "The address of digit = 24650\. */

We can get the value of the variable digit from its address using another operator * (asterisk), called the indirection or dereferencing or value at address operator.

printf("The value of digit = %d.", *(&digit);
 /* prints "The value of digit = 42\. */

2. Definition and Notation

The address of a variable can be stored in another variable known as a pointer variable. The syntax for storing a variable’s address to a pointer is:

dataType *pointerVariableName = &variableName;

For our digit variable, this can be written like this:

int *addressOfDigit = &digit;

or like this:

int *addressOfDigit;
addressOfDigit= &digit;

Declaration and Definition

This can be read as - A pointer to _int_ (integer) _addressOfDigit_ stores the _address of(&)_``_digit_ variable.

Few points to understand:

dataType – We need to tell the computer what the data type of the variable is whose address we are going to store. Here, int was the data type of digit.

It does not mean that addressOfDigit will store a value of type int. An integer pointer (like addressOfDigit) can only store the address of variables of integer type.

int variable1;
int variable2;
char variable3;
int *addressOfVariables;

* – A pointer variable is a special variable in the sense that it is used to store an address of another variable. To differentiate it from other variables that do not store an address, we use * as a symbol in the declaration.

Here, we can assign the address of variable1 and variable2 to the integer pointer addressOfVariables but not to variable3 since it is of type char. We will need a character pointer variable to store its address.

We can use our addressOfDigit pointer variable to print the address and the value of digit as below:

printf("The address of digit = %d.", addressOfDigit);
 /* prints "The address of digit = 24650." */
printf("The value of digit = %d.", *addressOfDigit);
 /*prints "The value of digit = 42\. */

Here, *addressOfDigit can be read as the value at the address stored in _addressOfDigit_.

Notice we used %d as the format identifier for addressOfDigit. Well, this is not completely correct. The correct identifier would be %p.

Using %p, the address is displayed as a hexadecimal value. But the memory address can be displayed in integers as well as octal values. Still, since it is not an entirely correct way, a warning is shown.

int num = 5;
int *p = #
printf("Address using %%p = %p",p);
printf("Address using %%d = %d",p);
printf("Address using %%o = %o",p);

The output according to the compiler I’m using is the following:

Address using %p = 000000000061FE00
Address using %d = 6422016
Address using %o = 30377000

This is the warning shown when you use %d - " warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ ".

3. Some Special Pointers

The Wild Pointer
char *alphabetAddress; /* uninitialised or wild pointer */
char alphabet = "a";
alphabetAddress = &alphabet; /* now, not a wild pointer */

When we defined our character pointer alphabetAddress, we did not initialize it.

Such pointers are known as wild pointers. They store a garbage value (that is, memory address) of a byte that we don’t know is reserved or not (remember int digit = 42;, we reserved a memory address when we declared it).

Suppose we dereference a wild pointer and assign a value to the memory address it is pointing at. This will lead to unexpected behaviour since we will write data at a memory block that may be free or reserved.

Null Pointer

To make sure that we do not have a wild pointer, we can initialize a pointer with a NULL value, making it a null pointer.

char *alphabetAddress = NULL /* Null pointer */ 

A null pointer points at nothing, or at a memory address that users can not access.

Void Pointer

A void pointer can be used to point at a variable of any data type. It can be reused to point at any data type we want to. It is declared like this:

void *pointerVariableName = NULL;

Since they are very general in nature, they are also known as generic pointers.

With their flexibility, void pointers also bring some constraints. Void pointers cannot be dereferenced as any other pointer. Appropriate typecasting is necessary.

void *pointer = NULL;
int number = 54;
char alphabet = "z";
pointer = &number;
printf("The value of number = ", *pointer); /* Compilation Error */
/* Correct Method */
printf("The value of number = ", *(int *)pointer); /* prints "The value at number = 54" */
pointer = &alphabet;
printf("The value of alphabet = ", *pointer); /* Compilation Error */
printf("The value of alphabet = ", *(char *)pointer); /* prints "The value at alphabet = z */

Similarly, void pointers need to be typecasted for performing arithmetic operations.

Void pointers are of great use in C. Library functions malloc() and calloc() which dynamically allocate memory return void pointers. qsort(), an inbuilt sorting function in C, has a function as its argument which itself takes void pointers as its argument.

Dangling Pointer

A dangling pointer points to a memory address which used to hold a variable. Since the address it points at is no longer reserved, using it will lead to unexpected results.

main(){
  int *ptr;
  ptr = (int *)malloc(sizeof(int));
  *ptr = 1;
  printf("%d",*ptr); /* prints 1 */
  free(ptr); /* deallocation */
  *ptr = 5;
  printf("%d",*ptr); /* may or may not print 5 */
}

Though the memory has been deallocated by free(ptr), the pointer to integer ptr still points to that unreserved memory address.

#c #developer

Pointers in C Explained – They're Not as Difficult as You Think
2.90 GEEK