Pointers in C/C++

Basics

Important

The codes in examples are executed on Linux (Ubuntu 16.04 64 bit) with g++ version 5.4.0 (thread model- Posix).

Here, when we refer to 'memory', it means we are talking about only RAM, as program is executed here only.

Before understanding pointers, first understand how memory is handled by C/C++ compiler.

Let us see a loosely connected example. Consider these situations.

  1. There are a1, a2, a3,...........,an, houses in a city.
  2. The task is to deliver letters from post-office to these houses.
  3. One option is to allot each house to each b1, b2, b3,...........,bn, post-men and give them job to deliver letters.
  4. Second option is to allot each house, a unique address and then tell a single post-man to deliver a particular letter to the particular house using those unique addresses.

The post-men in first option are our normal variables defined in our code

The post-man in second option is our pointer variable defined in our code.

A pointer is a variable which stores the address of another variable of same-data-type, to access their values.

Index

  1. Basics
  2. Variables
  3. Address-of Operator (&)
  4. Declaring a pointer
  5. Storing address in a Pointer
  6. De-reference/ Indirection Operator
  7. Pointer to a pointer or Pointer to pointer or Double Pointer
  8. Size of Pointer
  9. Pointer pointing to an array
  10. Arithmetic operations on Pointers
  11. Some more Results
  12. Different types of Pointers
    1. int *ptr
    2. const int *ptr
    3. int const *ptr
    4. int *const ptr
    5. const int *const ptr
  13. Implementation of 2-D array
  14. Pointer to a 2-D array
  15. Dynamic Allocation
  16. malloc()
  17. calloc()
  18. realloc()
  19. free()
  20. Implementing malloc()
  21. Dynamic allocation of 1-D array
  22. Dynamic allocation of 1-D array using calloc()
  23. Dynamic re-allocation of 1-D array using realloc()
  24. Dynamic allocation of 2-D array
  25. Wild pointer and importance of NULL

Variables

Variables are locations in the computer's memory which can be accessed by their identifier (their name). This way, the program does not need to care about the physical address of the data in memory; it simply uses the identifier whenever it needs to refer to the variable. Or we can say that whenever we run the code, the memory is allotted to variables and their addresses are assigned to the identifiers. When a variable is initialized, the memory needed to store its value is allocated at a specific location in memory.

Generally, C/C++ programs do not actively decide the exact memory addresses where its variables are stored. Fortunately, that task is left to the environment where the program is run - generally, an operating system that decides the particular memory locations on runtime. However, it may be useful for a program to be able to obtain the address of a variable during runtime in order to access data cells that are at a certain position relative to it.

Address-of Operator(&)

Pointers are special kind of variables which store address of another variable of same type.

The address of any variable can be obtained by putting '&' (address-of operator) before it.

Type the following C code:
#include <stdio.h> int main() { int a = 10; // 'a' is declared and initialized printf( " %d " , a); printf( "\n\n %p " , &a); /* '&a' returns the address of variable 'a', and it is a hexadecimal value. */ printf( "\n\n %u " , &a); /* '&a' returns the address of variable 'a', but this time, due to '%u',the return value is converted into 'unsigned int'. */ return 0; }

Output

10 0x7fff35b1ee34 900853300

0x7fff35b1ee34 or 900853300 is the address of variable 'a'. (Value of address will vary every time you run the code.)

Pointers have huge advantage as they store address of other variables. We will discuss about them later, first we should learn how to use them.

For our ease, in rest of our examples and codes, we will use %u and implicitly convert address from hexadecimal to unsigned decimal value.

Declaring a Pointer

General Method: <data-type> *<identifier>

Example:
int *ptr1; float *ptr2; char *ptr3;

As defined above- Pointers are special kind of variables which store address of another variable of same type.

Here 'ptr1'; is a pointer variable of integer type. Hence it can store addresses of only integer variables.

'ptr2'; is a pointer variable of float ype. Hence it can store addresses of only float variables.

'ptr3'; is a pointer variable of character type. Hence it can store addresses of only character variables.

Storing address in a Pointer

Run this code. (Note: the value of address will vary on every system and every-time you run code.)

#include <stdio.h> int main() { int a = 10; // 'a' is declared and initialized int *ptr1 = NULL; // '*ptr1' is declared and initialized ptr1 = &a; // address of 'a' is stored in 'ptr1' printf( " %u " , &a); printf( "\n\n %u " , ptr1); return 0; }

Output

3019103324 3019103324

De-reference/ indirection operator (*)

De-referencing means accessing value stored at particular address.

This operator has two usages, to declare a pointer and to access the data pointed by the pointer, i.e. access the value, stored in variable, whose address is with the pointer.

For example, in previous code, the variable 'a' has value 10. The address of 'a' is stored in 'ptr1'.

So printf("%u", ptr1), will print the address of 'a'.
And, printf("%d", *ptr1), will print the value stored at that address. So, it will print '10'.

#include <stdio.h> int main() { int a = 10; // 'a' is declared and initialized int *ptr1 = NULL; // '*ptr1' is declared and initialized ptr1 = &a; // address of 'a' is stored in 'ptr1' printf( " %d " , a); printf( "\n %u " , ptr1); printf( "\n %u " , *ptr1); return 0; }

Output

10 3019103549 10

Pointer pointing to a pointer
OR
Pointer to pointer
OR
Double pointer/ Triple pointer

Pointer to a pointer means, "A pointer which stores the address of another pointer".

This may sound confusing, but take it in simple way. The concept of pointers is to store address of variables of same data type.

As we defined, "Pointers are special kind of variables which store address of another variable of same type". But, Pointers are also variables. Though the value, inside them is an address, but this value can be changed.

For example:

#include <stdio.h> int main() { int a = 10, b = 37; // 'a' and 'b' are declared and initialized int *ptr1 = NULL; // '*ptr1' is declared and initialized ptr1 = &a; // address of 'a' is stored in 'ptr1' printf( " %d " , a); printf( "\n %u " , ptr1); printf( "\n %u " , *ptr1); ptr1 = &b; // address of 'b' is stored in 'ptr1' printf( "\n %d " , b); printf( "\n %u " , ptr1); printf( "\n %u " , *ptr1); return 0; }

Output

10 1351588296 10 37 1351588300 37

Coming back to the topic.

We understood the theoretical concept of pointer-to-pointer, now we will use this concept to implement double, triple pointer and so on.

Let's have some variables.

int c = 45; int *ptr1 = &c; int **ptr2 = &ptr1; int ***ptr3 = &ptr2;
Note:
  • Single-pointer-variable can save the address of a variable.
  • Double-pointer-variable can save the address of a single-pointer-variable.
  • Triple-pointer-variable can save the address of a double-pointer-variable. And so on..

Not doing so will show error for "type-mismatch", until the data-type is changed explicitly.

This is because, single-pointer: it has to point a data.

Double-pointer: it has to point a pointer, which in points a data.

Triple-pointer: it has to point a pointer, which again points another pointer, which points a data

And this chain extends and goes same for multi-dimensional pointers.




The de-reference/ indirection operator (*): to understand things more clearly, interpret it as:

'*' - go to the address and access the value.

printf(" \n%u ", c); Output: 45
printf(" \n%u ", ptr1); Prints the value stored in 'ptr1', i.e. address of 'c'
printf(" \n%u ", *ptr1);
  1. *ptr1 -> goes to the address.
    Thus, goes to location of 'c'

  2. Now access the value stored in it.
    Thus, prints 45.
printf(" \n%u ", ptr2); Prints the value stored in 'ptr2', i.e. address of 'ptr1'
printf(" \n%u ", *ptr2);
  1. *ptr2 -> goes to the address.
    Thus, goes to location of 'ptr1'

  2. Now access the value stored in it.
    Thus, prints the address of 'c'.
printf(" \n%u ", **ptr2);

When two operators of same precedence are used, they are evaluated according to their associativity.

Since the dereference/indirection operator has associativity from right to left, **ptr2 is evaluated as *(*ptr2).

So,
  1. *ptr2 -> goes to the address.
    Thus, goes to location of 'ptr1'

  2. Now access the value stored in it.
    Thus, got the address of 'c'.
    Thus, *ptr2 = ptr1.
    Thus, **ptr2 = *(*ptr2) = *(ptr1)

  3. *ptr1 -> goes to the address.
    Thus, goes to location of 'c'

  4. Now access the value stored in it.
    Thus, prints 45.
printf(" \n%u ", ptr3); Prints the value stored in 'ptr2', i.e. address of 'ptr2'
printf(" \n%u ", *ptr3);
  1. *ptr3 -> goes to the address.
    Thus, goes to location of 'ptr2'

  2. Now access the value stored in it.
    Thus, prints the address of 'ptr1'.
printf(" \n%u ", **ptr3); **ptr3 is evaluated as *(*ptr3). So,
  1. *ptr3 -> goes to the address.
    Thus, goes to location of 'ptr2'

  2. Now access the value stored in it.
    Thus, got the address of 'ptr1'.
    Thus, *ptr3 = ptr2.
    Thus, **ptr3 = *(*ptr3) = *(ptr2)

  3. *ptr2 -> goes to the address.
    Thus, goes to location of 'ptr1'

  4. Now access the value stored in it.
    Thus, prints the address 'c'.
printf(" \n%u ", ***ptr3); ***ptr3 is evaluated as *(*(*ptr3)). So,
  1. *ptr3 -> goes to the address.
    Thus, goes to location of 'ptr2'

  2. Now access the value stored in it.
    Thus, got the address of 'ptr1'.
    Thus, *ptr3 = ptr2.
    Thus, **ptr3 = *(*ptr3) = *(ptr2)

  3. *ptr2 -> goes to the address.
    Thus, goes to location of 'ptr1'

  4. Now access the value stored in it.
    Thus, got the address 'c'.
    Thus, *ptr2 = ptr1.
    Thus, ***ptr3 = *(*(*ptr3)) = *(*(ptr2)) = *ptr1

  5. *ptr1 -> goes to the address.
    Thus, goes to location of 'c'

  6. Now access the value stored in it.
    Thus, prints 45.

Size of Pointer

Since whole and soul purpose of pointers is to store address, they must contain an integral value. Hence their size is equal to that of 'int' or 'long', this depends on system and compiler. On my system (specs given in introduction), it is equal to 'long'.

int *ptr1; float *ptr2; char *ptr3;

char*, float*, int*, all are of same size and contain integral value. Though they are defined specifically as 'char', 'float', 'int' etc. This is done so that compiler should know that what kind of data it is going to point to. So, it becomes easy for it to de-reference it. This is all done to handle data properly. Let's look at the code:

#include <stdio.h> int main() { printf( " %u " , sizeof( int* )); printf( "\n %u " , sizeof( char* )); printf( "\n %u " , sizeof( float* )); return 0; }

Output

8 8 8

Pointer pointing to an array

In normal conditions too, arrays are implemented using concept, similar to pointers.

int arr[ 5 ];

Here the 5-consecutive memory-blocks for 'int' type are allotted and address of 1st element is stored in variable 'arr'. Accessing first element will give access to other elements as well because their memory addresses are in consecutive order.

Let us learn through various codes:

#include <stdio.h> int main() { int arr[ 5 ]; printf( " %u ", arr); printf( "\n %u ", &arr[ 0 ]); return 0; }

Output

4146104064 4146104064

Both are identical. Hence it is clear that identifier of array stores the address of 1st element of the array.

Thus, if we store the address, stored in 'arr', into a pointer, it should also work well. Let's try:

#include <stdio.h> int main() { int arr[ 5 ] = {3,43,23,4,2}; int *b = NULL; b = arr; printf( " %u ", *b); printf( "\n\n %u ", arr[ 0 ]); return 0; }

Output

3 3

Note: We didn't used '&' while initializing pointer 'b'. It is because 'arr' itself is storing address of 1st element of the array.

Hence, 'arr' is equivalent to 'b' but NOT equal to 'b'.



This code does't works:

#include <stdio.h> int main() { int arr[ 5 ] = {3,43,23,4,2}, c = 10; arr = &c; printf( " \n%u ", arr); return 0; }

Compilation Error:

trial.c: In function 'main': trial.c:6:7: error: assignment to expression with array type arr = &c; ^ trial.c:8:11: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'int *' [-Wformat=] printf( " \n%u ", arr); ^

As said, 'arr' is equivalent to pointer 'b' (here, 'b' is referred to the pointer vaiable declared in previous working example.), but NOT equal to pointer 'b'. As once allotted, the address stored in 'arr' cannot be changed.

Arithmetic operations on Pointers

Consider this code:
#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}; int *b = NULL; b = arr; for(i = 0 ; i < 5; i++) printf( "\n %u ", *(b+i)); return 0; }

Output

3 43 23 4 2

Confused?

Now it is obvious to think that there is something wrong. If the hypothetical address of first element of 'arr' is 8542, you will think:

b = 8542                                                *b = 3

b + i = 8543                                           *b = <some-garbage-value>

Right??

Wrong. Now this is where declaring the data-type of pointer comes into play.

Basically:

b + 1 ≠ 8543

The compiler knows 'b' is a pointer and it also knows that 'b' is an integer pointer. Hence it knows that integers are consist of 4 consecutive bytes (size of int may change from compiler to compiler and machine to machine). Thus:

(b + 1) = 8542 + sizeof(int)

In General: <pointer + 1> = <address-value-inside-pointer + sizeof(data-type-of-pointer)>

In Linux, 64 bit, gcc/g++ compiler, size of int is 4 bytes.

i = 0 (b + i) (b + 0) address of arr[0] *(b + 0) 3
i = 1 (b + i) (b + 1) address of arr[1] *(b + 1) 43
i = 2 (b + i) (b + 2) address of arr[2] *(b + 2) 23
i = 3 (b + i) (b + 3) address of arr[3] *(b + 3) 4
i = 4 (b + i) (b + 4) address of arr[4] *(b + 4) 2

Some more Results

This code works:

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}; int *b = NULL; b = arr; for(i = 0 ; i < 5; i++) printf( "\n %u ", b[i]); return 0; }

Output

3 43 23 4 2

Though 'b' is a pointer, but above results conclude:

b[0] is treated same as *(b + 0)
b[1] is treated same as *(b + 1)
b[2] is treated same as *(b + 2)
b[3] is treated same as *(b + 3)
b[4] is treated same as *(b + 4)

What will happen if we prefix &i with dereference operator?

& or address-of operator returns the address of the variable to which it is added as prefix. And the work of dereferenc/ indirection operator is to go to the address and access the value.

So, if we prefix &i with dereference/ indirection operator, then * will nullify the effect of address-of (&) operator.

*(&i) is same as i
t
*(&b[i]) is same as *(&*(b + i)) is same as *(b + i) is same as b[i]




Since b is a pointer and *(b + i) works same as b[i].

Thus, *(a + i) should also work. as 'arr' is equivalent to a pointer.

This code works:

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}; int *b = NULL; b = arr; for(i = 0 ; i < 5; i++) printf( "\n %u ", *(arr + i)); return 0; }

Output

3 43 23 4 2

This again confirms the similarity between pointer and array. As, though it is a normal array, the de-reference/indirection operator works perfectly on 'arr'.

This is because 'arr' stores the address, and the work of dereference/ indirection operator is to go to the address stored in it and access the value.





This code works:

#include <stdio.h> int main() { int arr[ 5 ] = {3,43,23,4,2}; int *b = NULL, i; b = arr; /* or you can also write ptr=&a[0]; as both 'a' and
'ptr' are pointers */
for(i = 0 ; i < 5; i++) printf( " %u ", *(b + i)); printf( "\n"); for(i = 0 ; i < 5; i++) { printf( " %u ", *b); b++;// or b+ = 1 } printf( "\n"); for(i = 0 ; i < 5; i++) printf( " %u ", arr[i]); return 0; }

Output

3 43 23 4 2 3 43 23 4 2 3 43 23 4 2

VER VERY IMPORTANT NOTE:
'b' is pointing to element of the array 'arr'. Not the whole array. Not to be confused.

So when you print *(b + i) or something equivalent. Do check that the address doesn't go out of range of array.

It will not show error, but it will display some garbage value present at that unused memory.

For example, for the above code if we try to print, arr[6] or *(b + 6), it will not show error, but it will show the value present at that address.

Different types of Pointers



int *ptr;

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}; int *ptr = NULL; ptr = arr; for(i = 0 ; i < 5; i++) *(ptr + i) = i; for(i = 0 ; i < 5; i++) printf( "\n %u ", *(ptr + i)); return 0; }

Output

0 1 2 3 4

Conclusion:

  1. The data pointed by ptr can be modified using pointer. (i.e. *ptr = 1;    *ptr=2;    is valid).
  2. The data of array can be modified (i.e a[0]=1;     a[0] = 2;    is valid).
  3. Adressed stored in ptr can be modified i.e ptr=&b;    ptr = &c; (assuming int b,c;), is valid.




const int *ptr;    or    int const *ptr;

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}; const int *ptr = NULL; ptr = arr; for(i = 0 ; i < 5; i++) *(ptr + i) = i; for(i = 0 ; i < 5; i++) printf( " \n%u ", *(ptr + i)); return 0; }

Compilation error

t3.cpp: In function 'int main()': t3.cpp:10:10: error: assignment of read-only location '*(ptr + ((sizetype)(((long unsigned int)i) * 4ul)))' *(ptr+i)=i; ^

Conclusion:

  1. The data pointed by ptr cannot be modified using pointer. (i.e. *ptr = 1;    *ptr = 2;    is invalid)
  2. Adressed stored in ptr can be modified i.e ptr = &b;    ptr = &c;    (assuming int b,c;), is valid.
  3. The data of array can be modified (i.e arr[0]=1;    arr[0]=2;    is valid), but it cannot be modified using pointer when using const int* b;
  4. *ptr = 1;    *ptr = 2;    *(ptr + 1) = 1;    *(ptr + 1) = 2    are all invalid.




int *const ptr;

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}; int *const ptr = arr; for(i = 0 ; i < 5; i++) *(ptr + i) = i; for(i = 0 ; i < 5; i++) printf( "\n %u ", *(ptr + i)); return 0; }

Output

0 1 2 3 4

BUT!!

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}, b = 10; int *const ptr = arr; ptr = &b; for(i = 0 ; i < 5; i++) *(ptr + i) = i; for(i = 0 ; i < 5; i++) printf( "\n %u ", *(ptr + i)); return 0; }

Compilation error

t3.cpp: In function 'int main()' t3.cpp:7:6: error: assignment of read-only variable 'ptr' ptr=&b; ^

Conclusion:

  1. The data pointed by ptr can be modified using pointer. (i.e. *ptr = 1;    *ptr = 2;    is valid).
  2. The data of array can be modified (i.e a[0]=1;    a[0]=2;    is valid).
  3. Adressed stored in ptr cannot be modified i.e ptr = &b;    ptr = &c;    (assuming int b,c;), is invalid.




const int *const ptr;

#include <stdio.h> int main() { int i, arr[ 5 ] = {3,43,23,4,2}, b = 10; const int *const ptr = &b; ptr = arr; for(i = 0 ; i < 5; i++) *(ptr + i) = i; for(i = 0 ; i < 5; i++) printf( "\n %u ", *(ptr + i)); return 0; }

Compilation error

trial.c: In function 'main': trial.c:8:7: error: assignment of read-only variable 'ptr' ptr = arr; ^ trial.c:11:15: error: assignment of read-only location '*(ptr + (sizetype)((long unsigned int)i * 4ul))' *(ptr + i) = i; ^

Conclusion:

  1. The data pointed by ptr cannot be modified using pointer. (i.e. *ptr = 1;    *ptr = 2;    is invalid).
  2. The data of array can be modified (i.e a[0] = 1;     a[0] = 2;     is valid).
  3. Adressed stored in ptr cannot be modified i.e ptr = &b;    ptr = &c;     (assuming int b,c;), is invalid.

Implementation of 2-D array

Okay so till now we know that 2-D array consists of rows and columns. But do they exactly look like a matrix on the memory?

We refer to consecutive blocks for 1-D array, matrix for 2-D array, cube for 3-D array. But then what for 4-D, 5-D and further dimensional array?

Note: We "Refer".

Actually, all different multi-dimensional arrays are stored in form of 1-D array, i.e. consecutive blocks of memory. That is something huge to digest, but this is how everything works.

Let us consider this code first for better understanding.

#include <stdio.h> int main() { int i, j, arr2[ 3 ] [ 5 ]; /*printing address of each element of array*/ for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) printf( "\n Address of arr2[%d][%d]: %u", i, j, &arr2[i][j]); /*printing the value inside arr2*/ printf( "\n\n Value inside arr2: %u\n", arr2); /*printing the address of arr2[i][0]*/ for(i = 0 ; i < 3; i++) printf( "\n\t Address of arr2[%d][0]: %u", i, &arr2[i][0]); /*printing the value inside arr2[i]*/ for(i = 0 ; i < 3; i++) printf( "\n Value inside arr2[%d]: %u", i, arr2[i]); /*printing the value inside *(arr2 + i)*/ for(i = 0 ; i < 3; i++) printf( "\n\tValue inside arr2[%d] *(arr2 + i) style: %u", i, *(arr2 + i)); /*printing the addrss of arr2[i]*/ for(i = 0 ; i < 3; i++) printf( "\n Address of arr2[%d]: %u", i, &arr2[i]); return 0; }

Output

Address of arr2[0][0]: 2675608160 Address of arr2[0][1]: 2675608164 Address of arr2[0][2]: 2675608168 Address of arr2[0][3]: 2675608172 Address of arr2[0][4]: 2675608176 Address of arr2[1][0]: 2675608180 Address of arr2[1][1]: 2675608184 Address of arr2[1][2]: 2675608188 Address of arr2[1][3]: 2675608192 Address of arr2[1][4]: 2675608196 Address of arr2[2][0]: 2675608200 Address of arr2[2][1]: 2675608204 Address of arr2[2][2]: 2675608208 Address of arr2[2][3]: 2675608212 Address of arr2[2][4]: 2675608216 Value inside arr2: 2675608160 Address of arr2[0][0]: 2675608160 Address of arr2[1][0]: 2675608180 Address of arr2[2][0]: 2675608200 Value inside arr2[0]: 2675608160 Value inside arr2[1]: 2675608180 Value inside arr2[2]: 2675608200 Value inside arr2[0 *(arr2 + i) style]: 2675608160 Value inside arr2[1 *(arr2 + i) style]: 2675608180 Value inside arr2[2 *(arr2 + i) style]: 2675608200 Address of arr2[0]: 2675608160 Address of arr2[1]: 2675608180 Address of arr2[2]: 2675608200

First let us take look a on the addresses of arr2[i][j]. They are all consecutive. Hence, we can conclude that a single chain of blocks (each of 4 bytes) is allotted to our 2-D array, same can be checked for 3-D or 4-D array.

t
[0] [0] [0] [1] [0] [2] [0] [3] [0] [4] [1] [0] [1] [1] [1] [2] [1] [3] [1] [4] [2] [0] [2] [1] [2] [2] [2] [3] [2] [4]

So, what is happening?

⇴ When you access arr2[0][0], for you it is, 1st column of 1st row. But for C/C++ it is, 1st element of 1st memory group of 5 ('5' because we have defined number of columns to be five).

⇴ When you access arr2[1][3], for you its 4th element of 2nd row, but for C/C++ it is 4th element of 2nd memory group of 5. When it reads arr2[1], it jumps 5 blocks from 1st element, i.e. it reaches to 2nd memory group and then access its 4th element.

Note: Do not get confused with row-major-form and column-major-form. That has to do something with how you have stored data. But memory is allotted in the same sequence depicted above. And this is also confirmed by the above code.

As we said before, C/C++ is a language, not for your machine, but for your compiler.

Observations:

  1. arr2 and arr2[0] conatin same value, that is, the address of arr2[0][0].

  2. Address of arr[0], arr[1], arr[2] is same as of address of first column of each row, i.e.,

    &arr[0] is same as &arr[0][0] is same as arr2 + 0
    &arr[1] is same as &arr[1][0] is same as arr2 + 1
    &arr[2] is same as &arr[2][0] is same as arr2 + 2

Conclusion

  1. There is no real, physical existence of arr2[0], arr2[1] and arr2[2]. They are handled virtually. They are just sytanx for which the C/C++ returns the address of first column for particular rows.

  2. arr2 point to whole row. It is of 'int (*ptr)[]' type. It means for them bytes acquired by no_of_elements define in '[ ]' is considered as one single block of memory. So if you apply arithmetic operation on them, they won't add sizeof(<data-type>) to the address stored in them. They will add the total number of bytes stored in that memory block.. (This is to be understood properly as things will change during dynamic allocation of 2-D array).

  3. (On my system size of int is 4) When 2-D array is declared and initialized, referring to above code, 3 * 5 * sizeof(int) = 60 bytes are alloted for array and 4 bytes are alloted for arr2 which points to first element of memory sequence of 2-D array.

We saw, 2-D array also work on the concept similar to pointers. Means they should implement arithmetic operations. They do, but not it that simple way.

  1. *(arr2 + i), returns the address of first element of ith row.

  2. If we dereference it again, it will access the value at that address. Hence, *(*(arr2 + i)) will access the value stored in 1st column of ith row.

  3. Now we saw, arr2 + i or arr2[i] returns the address of 1st column of ith row. We also saw, how memory is alloted for 2-D array. Thus if we do this, *(*(arr2 + i) + j), we will be able to access jth column of ith row.

Consider this code:

#include <stdio.h> int main() { int i, j, arr2[ 3 ] [ 5 ]; for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) *(*(arr2 + i) + j) = 5 * i + j; for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) printf( "\n Value inside arr2[%d][%d]: %u", i, j, arr2[i][j]); return 0; }

Output

Value inside arr2[0][0]: 0 Value inside arr2[0][1]: 1 Value inside arr2[0][2]: 2 Value inside arr2[0][3]: 3 Value inside arr2[0][4]: 4 Value inside arr2[1][0]: 5 Value inside arr2[1][1]: 6 Value inside arr2[1][2]: 7 Value inside arr2[1][3]: 8 Value inside arr2[1][4]: 9 Value inside arr2[2][0]: 10 Value inside arr2[2][1]: 11 Value inside arr2[2][2]: 12 Value inside arr2[2][3]: 13 Value inside arr2[2][4]: 14

Result: First we accessed the jth column of ith row, using *(*(arr2 + i) + j) while storing values. Then we accessed it using arr2[i][j] while printing. That means:

arr2[i][j]    is same as    *(*(arr2 + i) + j)

Pointer to a 2-D array

Now we know from above examples that how 2-D array is implemented on our memory.

So, we will try to access 2-D array with the help of a pointer.

Note: This is different from what we saw just above. Because as we told, C/C++ is a language for your compiler. When you declare a 2-D array arr2[3][5], C/C++ knows that arr2 is the identifier of given 2-D array and the meaning of arr2[i] is to return the address of 1st element of ith row.

But when we store the value of arr2 in a pointer, pointer does not know what is 2-D or 3-D array, it only knows to point data and how to access it. It does not know that for 2-D array, ptr[i] has to return the address of ith row. For pointer 2-D array is same as 1-D array, because it is saved in the same form in the memory, a consecutive sequence of integer bytes. Thus, ptr[i] is same as *(ptr + i). Thus it will go at that address and access its value.

But, we know how 2-D array is saved in our memory (in form of 1-D array), we formulated this equation.

For 3 x 5 2-D array, (i,j)th element will be *(ptr + (i * 5) + j) where 0 ≤ i < 3.

For general: *(<pointer_name> + (i * no_of_cols) + j) where 0 ≤ i < no_of_rows and 0 ≤ i < no_of_cols.

#include <stdio.h> int main() { int i, j, *ptr, arr2[ 3 ] [ 5 ]; ptr = arr2; for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) arr2[i][j] = 6 * i + j * 2;//saving any non-realted value for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) printf( "\n Value inside arr2[%d][%d]: %u", i, j, arr2[i][j]); for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) printf( "\n\tValue inside arr2[%d][%d] *(ptr+(i*5)+j): %u", i, j, *(ptr+(i*5)+j)); return 0; }

Output

Value inside arr2[0][0]: 0 Value inside arr2[0][1]: 2 Value inside arr2[0][2]: 4 Value inside arr2[0][3]: 6 Value inside arr2[0][4]: 8 Value inside arr2[1][0]: 6 Value inside arr2[1][1]: 8 Value inside arr2[1][2]: 10 Value inside arr2[1][3]: 12 Value inside arr2[1][4]: 14 Value inside arr2[2][0]: 12 Value inside arr2[2][1]: 14 Value inside arr2[2][2]: 16 Value inside arr2[2][3]: 18 Value inside arr2[2][4]: 20 Value inside arr2[0][0] *(ptr+(i*5)+j): 0 Value inside arr2[0][1] *(ptr+(i*5)+j): 2 Value inside arr2[0][2] *(ptr+(i*5)+j): 4 Value inside arr2[0][3] *(ptr+(i*5)+j): 6 Value inside arr2[0][4] *(ptr+(i*5)+j): 8 Value inside arr2[1][0] *(ptr+(i*5)+j): 6 Value inside arr2[1][1] *(ptr+(i*5)+j): 8 Value inside arr2[1][2] *(ptr+(i*5)+j): 10 Value inside arr2[1][3] *(ptr+(i*5)+j): 12 Value inside arr2[1][4] *(ptr+(i*5)+j): 14 Value inside arr2[2][0] *(ptr+(i*5)+j): 12 Value inside arr2[2][1] *(ptr+(i*5)+j): 14 Value inside arr2[2][2] *(ptr+(i*5)+j): 16 Value inside arr2[2][3] *(ptr+(i*5)+j): 18 Value inside arr2[2][4] *(ptr+(i*5)+j): 20

Though it shows warning for incompatible pointer assignment on some compilers. But it works well.





Correct way: int *ptr[]

Like we define integer array, int arr[] of which each element is an integer; here we will define integer-pointer array, int *ptr[], of which element is an integer-pointer. (Note: int *ptr[] is very-very different from int (*ptr)[]).

Now for the above array of 3x5 type, we will define integer pointer array with 3 elements.

#include <stdio.h> int main() { int i, j, arr2[ 3 ] [ 5 ]; int *ptr[3] = { arr2[0], arr2[1], arr2[2]}; for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) arr2[i][j] = 6 * i + j * 2;//saving any non-realted value for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) printf( "\n Value inside arr2[%d][%d]: %u", i, j, arr2[i][j]); for(i = 0 ; i < 3; i++) for(j = 0 ; j < 5; j++) printf( "\n\tValue inside arr2[%d][%d] *((ptr+i)+j): %u", i, j, *(*(ptr+i)+j)); return 0; }

Output

Value inside arr2[0][0]: 0 Value inside arr2[0][1]: 2 Value inside arr2[0][2]: 4 Value inside arr2[0][3]: 6 Value inside arr2[0][4]: 8 Value inside arr2[1][0]: 6 Value inside arr2[1][1]: 8 Value inside arr2[1][2]: 10 Value inside arr2[1][3]: 12 Value inside arr2[1][4]: 14 Value inside arr2[2][0]: 12 Value inside arr2[2][1]: 14 Value inside arr2[2][2]: 16 Value inside arr2[2][3]: 18 Value inside arr2[2][4]: 20 Value inside arr2[0][0] *((ptr+i)+j): 0 Value inside arr2[0][1] *((ptr+i)+j): 2 Value inside arr2[0][2] *((ptr+i)+j): 4 Value inside arr2[0][3] *((ptr+i)+j): 6 Value inside arr2[0][4] *((ptr+i)+j): 8 Value inside arr2[1][0] *((ptr+i)+j): 6 Value inside arr2[1][1] *((ptr+i)+j): 8 Value inside arr2[1][2] *((ptr+i)+j): 10 Value inside arr2[1][3] *((ptr+i)+j): 12 Value inside arr2[1][4] *((ptr+i)+j): 14 Value inside arr2[2][0] *((ptr+i)+j): 12 Value inside arr2[2][1] *((ptr+i)+j): 14 Value inside arr2[2][2] *((ptr+i)+j): 16 Value inside arr2[2][3] *((ptr+i)+j): 18 Value inside arr2[2][4] *((ptr+i)+j): 20

Observations:

  1. For arr2[3][5], we defined a pointer array of 3 elements, of which all three elements are also pointers of same data-type as of 2-D array.

  2. Now, we save the address of 1st element of 1st row in ptr[0], address of 1st element of 2nd row in ptr[1] and address of 1st element of 3rd row in ptr[2].

  3. And ptr (identifier) is pointing to ptr[0] as normally arrays do.

Thus:

ptr[0] is same as *(ptr + 0) is same as arr2[0] contains address of arr2[0][0]
ptr[i] is same as *(ptr + i) is same as arr2[i] contains address of arr2[i][0]

Now as we know that *(ptr + i) will return the address of 1st element of ith row, dereferencing it again will access the value at that place.

Thus, *(*(ptr+i)+j) = ptr[i][j] - same as- arr2[i][j], will access the (i,j)th element.

Note: There is no specific method. After knowing pointers and their working, we are just playing with one or the other method.

Dynamic Allocation

As, now we know how to handle pointers and how to deal with them. We can now make real use of them, that is Dynamic Allocation of memory.

For example, the arrays (integer array, character array, structure array etc.) you define in your code, are static. Once defined, their size cannot be modified during run time.

Suppose you made a program for general condition that inputs elements in an array. You define an empty array in your program with a fixed size. But what, if the fixed size is less than the number of elements you want to input in one of the test conditions?

Will you again modify your code and re-compile it?

So, here we take advantage of dynamic allocation. Through this we can ask the user that how much memory is needed, and then it will be allocated.

C has 3 predefined functions for allocating memory, and free() for de-allocation, under "stdlib.h" header file.

void* malloc(size_t /*size_in_bytes*/) void* calloc(size_t /*no_of_elements*/ , size_t /*size_in_bytes*/) void* realloc(void* /*address of previous data*/ , size_t /*size_in_bytes*/) free(void* /*address of dynamic data*/)
Note: here we will discuss the general working of these functions. For other exceptions and return values, read the documentation of your compiler.

malloc()

Click here to learn how to use malloc()

  1. Allocates a memory block of size, given to it as a parameter, in bytes. If it does it successfully, it returns the address of the first byte of the sequence.
  2. If it fails, it returns NULL.

  3. The value returned by it, is of 'void' type. Hence it does not know that the memory it has allocated will be used as integer or character or float array or something other

  4. When the value returned by it is stored in a pointer. The behavior of that dynamically allocated memory block is now defined by the data-type of that pointer.

  5. Hence, it is the duty of programmer to calculate the number of bytes to be allocated, accordingly.





void*

We know how to declare pointer: <identifier> *<data-type>. void is also a data type, as we know. So, pointer of type void points to void. It means it can point to variables of all data-types. This is an advantage because then we can use same memory block for storing either integer or char or float etc,

calloc()

Click here to learn how to use calloc()

It accepts the number of elements of array, and size of each element of the array as a parameter. It itself calculates the number of bytes to be allocated and rest working is same as of malloc.

realloc()

Click here to learn how to use realloc()

It re-allocates the memory, allocated by malloc/ calloc. It must be previously allocated by malloc(), calloc() or realloc() and not yet freed with a call to free or realloc. Otherwise, the results are undefined.

The reallocation is done by either:

  1. Expanding or contracting the existing area pointed to by ptr, if possible. The contents of the area remain unchanged up to the lesser of the new and old sizes. If the area is expanded, the contents of the new part of the array are undefined.

  2. allocating a new memory block of size new_size bytes, copying memory area with size equal the lesser of the new and the old sizes, and freeing the old block.

If there is not enough memory, the old memory block is not freed and NULL pointer is returned.

free()

To understand this, we should understand the memeory map of C program.

Stack
Heap
Global Variable Program code
  1. Stack- is used for storing all your static variables, function parameters, literals etc.

  2. Global variables- as names defines, stores the global variables of your program.

  3. Program code- as name defines, stores your executable program code.

  4. Heap- this is used for dynamic allocation.

Now all other memory blocks all handled by C, except heap. Heap is dedicated to be handled by user. Memory is allocated and freed, during run time, on function calls, by C for static variables. But it's not same for heap.

Once you allocate a memory, it is stored on heap, and will remain on heap till you free it or whole program is executed, because as we told, heap is dedicated to be manipulated by user..

For small programs, it is affordable but think of a situation where there is running a loop, and again and again malloc is called inside loop.

If you will not free the memory after using it, a time will come when whole heap will be filled and no more space will be left. This is called as 'memory leak'.

So, to avoid it, always free the memory after it is used.

Implementing malloc()

#include <stdio.h> #include <stdlib.h> int main() { int *ptr = NULL, i; ptr = (int*)malloc(sizeof(int)); if(ptr == NULL) { printf("Error: Could not allocate memory."); return 0; } *ptr = 5; printf( " \n%u ", *ptr); free(ptr); return 0; }

Output

5
  1. Here a block of sizeof(int) bytes is allocated on to the memory, and address of first byte is returned.

  2. We explicitly converted the void*, returned by malloc, to int* by placing (int*) before calling malloc.

  3. Then that dynamic memory can be accessed and manipulated.

Dynamic allocation of 1-D array

#include <stdio.h> #include <stdlib.h> int main() { int *ptr = NULL, i, n; printf("Enter the number of elements in your array: "); scanf("%d", &n); ptr = (int*)malloc(sizeof(int)* n); if(ptr == NULL) { printf("Error: Could not allocate memory."); return 0; } for(i = 0 ; i < n; i++) scanf("%d", ptr + i); for(i = 0 ; i < n; i++) printf( " %d ", *(ptr + i)); free(ptr); return 0; }

Output

Enter the number of elements in your array: 4 1 2 3 4 1 2 3 4

Dynamic allocation of 1-D array using calloc()

#include <stdio.h> #include <stdlib.h> int main() { int *ptr = NULL, i, n; printf("Enter the number of elements in your array: "); scanf("%d", &n); ptr = (int*)calloc(n, sizeof(int)); if(ptr == NULL) { printf("Error: Could not allocate memory."); return 0; } for(i = 0 ; i < n; i++) scanf("%d", ptr + i); for(i = 0 ; i < n; i++) printf( " %d ", *(ptr + i)); free(ptr); return 0; }

Output

Enter the number of elements in your array: 3 1 3 2 1 3 2

Dynamic re-allocation of 1-D array using realloc()

Before using re-alloc, you should have prior knowledge of using malloc() or calloc()

Click here to learn how to use malloc()

Click here to learn how to use calloc()

#include <stdio.h> #include <stdlib.h> int main() { int *ptr = NULL, i, n, m; printf("Enter the number of elements in your array: "); scanf("%d", &n); /*Allocation of 1-D array*/ ptr = (int*)calloc(n, sizeof(int)); if(ptr == NULL) { printf("Error: Could not allocate memory."); return 0; } /*Scanning elements of 1-D array*/ for(i = 0 ; i < n; i++) scanf("%d", ptr + i); /*Printing elements of 1-D array*/ for(i = 0 ; i < n; i++) printf( " %d ", *(ptr + i)); printf("Enter the number of elements in your new array: "); scanf("%d", &m); /*Re-allocation of 1-D array*/ ptr = (int*)realloc(ptr, sizeof(int) * m); if(ptr == NULL) { printf("Error: Could not allocate memory."); return 0; } if(m < n) { printf("\nYour reduced array."); for(i = 0 ; i < m; i++) printf( " \n%d ", *(ptr + i)); } else { printf("Elements present in your array."); for(i = 0 ; i < n; i++) printf( "\n%d", *(ptr + i)); printf("\nEnter new elements."); for( ; i < m; i++) scanf("%d", ptr + i); printf("\nYour expanded array."); for(i = 0 ; i < m; i++) printf( " \n%d ", *(ptr + i)); } free(ptr); return 0; }

Output:

Enter the number of elements in your array: 4 3 4 23 5 3 4 23 5 Enter the number of elements in your new array: 3 Your reduced array. 3 4 23

Output:

Enter the number of elements in your array: 4 32 5 56 3 32 5 56 3 Enter the number of elements in your new array: 6 Elements present in your array. 32 5 56 3 Enter new elements.44 3 Your expanded array. 32 5 56 3 44 3

Dynamic Allocation of 2-D array

You are suggested to acquire prior knowledge of using malloc() or calloc()

Click here to learn how to use malloc()

Click here to learn how to use calloc()

The dynamically allocated 2-D array is very much different than static one. Implementing dynamic allocation of 2-D array or 3-D array etc., is more like playing with pointers. To do this it is required that you read everything taught above in this website. The dynamically allocated 2-D array is very much different than static one. Implementing dynamic allocation of 2-D array or 3-D array etc., is more like playing with pointers. To do this it is required that you read everything taught above in this website.

Pseudo code:

/* Line 1*/ int **ptr2 = NULL; /* Line 2*/ int m; // number of rows /* Line 3*/ int n; // number of columns /* Line 4*/ ptr2 = (int**)malloc(sizeof(int*)* m); /* Line 5*/ for(i = 0 ; i < m; i++) ptr2[i] = (int*)malloc(sizeof(int)* n);

What happened?

First, we declared double pointer, **ptr2.

In line 3, we allocated a 1-D array with 'm' elements and address of its first element is stored in ptr2. Now, the special thing is, each element of this 1-D array is an integer-pointer.

Note: We wrote sizeof(int*) not sizeof(int), as both are not equal. We stated that we will discuss pointers using integers, but all the methods and algorithms are applicable on other data types too. So as we saw, we have to create an array, such that each element is a pointer, while calculating size, we should multiply 'm' with size of a pointer. Thus, in case of characters too, while doing same step, sizeof(char) will not be equal to sizeof(char*), and we want pointer array, so we will use sizeof(char*).

Now, in line 4, through loop, we created one 1-D array with 'n' integer elements in one iteration. And address of first element is stored in ptr2[i] or *(ptr2 + i). And we this for all possible values of i.

Let us assume m = 3 and n = 5

Addresses written under pointer/ integer variable are hypothetical.

Then the memory is allotted something like in this sequence.

  Pointer Variable.

  Integer Variable.


ptr2[0] 1000 1004 1008 1012 1016
ptr2[1] 1020 1024 1028 1032 1036
ptr2[2] 1040 1044 1048 1052 1056

We know how 2-D stay is allocated on memory, so using pointers we tried to map something same.

Thus to access (i,j)th element, we first need to access ith row. Therefore, we do *(ptr2 + i).

Now to access jth column of ith row, we do *(*(ptr2 +i) + j).

Now we have seen earlier,

<name>[i] is evaluated as: *(<name> + i)

<name>[i][j] is evaluated as: *(*(<name> + i) + j).

Thus for above pseudo code, the (i,j)th element can be accessed through ptr2[i][j].

Code:

#include <stdio.h> #include <stdlib.h> int main() { int **ptr2 = NULL, i, j; int m = 3, n = 5; ptr2 = (int**)malloc(sizeof(int*)* m); if(ptr2 == NULL) { printf("Error: Could not allocate memory."); return 0; } for(i = 0 ; i < m; i++) ptr2[i] = (int*)malloc(sizeof(int)* n); for(i = 0 ; i < m; i++) printf( "\nAddress of ptr2[%d]: %u", i, &ptr2[i]); for(i = 0 ; i < m; i++) for(j = 0 ; j < n; j++) printf( "\n\tAddress of ptr2[%d][%d]: %u", i, j, &ptr2[i][j]); for(i = 0 ; i < m; i++) free(ptr2[i]); free(ptr2); return 0; }

Output

Address of ptr2[0]: 25141264 Address of ptr2[1]: 25141272 Address of ptr2[2]: 25141280 Address of ptr2[0][0]: 25141296 Address of ptr2[0][1]: 25141300 Address of ptr2[0][2]: 25141304 Address of ptr2[0][3]: 25141308 Address of ptr2[0][4]: 25141312 Address of ptr2[1][0]: 25141328 Address of ptr2[1][1]: 25141332 Address of ptr2[1][2]: 25141336 Address of ptr2[1][3]: 25141340 Address of ptr2[1][4]: 25141344 Address of ptr2[2][0]: 25141360 Address of ptr2[2][1]: 25141364 Address of ptr2[2][2]: 25141368 Address of ptr2[2][3]: 25141372 Address of ptr2[2][4]: 25141376
  Pointer Variable.

  Integer Variable.


ptr2[0]

25141264
ptr2[0][0]

25141296
ptr2[0][1]

25141300
ptr2[0][2]

25141304
ptr2[0][3]

25141308
ptr2[0][4]

25141312
ptr2[1]

25141272
ptr2[1][0]

25141328
ptr2[1][1]

25141332
ptr2[1][2]

25141336
ptr2[1][3]

25141340
ptr2[1][4]

25141344
ptr2[2]

25141280
ptr2[2][0]

25141360
ptr2[2][1]

25141364
ptr2[2][2]

25141368
ptr2[2][3]

25141372
ptr2[2][4]

25141376

Observation

  1. ptr2=(int**)malloc(sizeof(int*)*m), 3 int-pointer blocks are allotted, and address of first block, 25141264, is given to ptr2.

  2. For i = 0, ptr2[0]=(int*)malloc(sizeof(int)*n), 5 int blocks are allotted and address of first block, 25141296, is given to ptr2[0]. And same goes for ptr2[1] and ptr[2].

Wild pointer and importnace of NULL

NULL is a macro which has a pre-defined meaning and that is equal to '0'. In case if you treat it as an address and try to de-reference it, it will show error.

Why?

Addresses are integers, so they must be starting from '0', somewhere in the memory. So why this error?

NULL a defined value, but one that is defined by the environment to not be a valid address for any member or object. And in most of the cases, your operating system resides at the starting of the addresses of RAM (memory), so they make that addresses to be un-accessible.

When you declare a pointer and keep it un-initialized, it is known as wild pointer. They contain some garbage (unexpected) value by default. So, if by-mistake, without initializing them, if you de-reference the value at that location and manipulate it, not knowing that, that particular memory could be in use by some other software which is running in background or maybe your OS is using it for storing temporary data, you may crash the system.

So, it is advised and also it is a good practice to initialize the pointer with NULL, so that by mistake if you dereference it, your code will show run-time error, without harming any data. Also, if you know you have stored NULL, you check the pointer before using it, to identify that whether the memory is allotted for your dynamic allocation or not or to avoid misbehavior due to logical error. This is because you know that if your pointer variable is not able to get initialized by some expected data, it will contain NULL, hence you can check it before using it and code safely.