Introduction
Nothing to say more than hello 👋, In Case your wondering who is this man in the banner it's the Khwarizmi the inventor of algorithms concept. Make sure to follow because I will start more advanced series in future.
Pointers
Introduction to pointers
In c programming language you visualize the memory as 1D array ,
When I call a , I am actually inside the cell.
Now how many cells will a consume? Will it depends on the implementation . many people think that char is always 1 byte and int is 2 byte , long is 4 byte but it is not fixed like that.
depend on which platform you program is running , every variable is going to take some size. But when size is decided it will be fixed. But you can not fix it that char is same in all the platforms , it is not true.
Most of the systems have the implementation of char to 1 byte. let's assume that in this platform the int will take 2 byte.
the other way is to access them using the address , so every entity that have been given some memory they will have an address , sometimes if you need to access a variable you can access it using the address and completely forget about name. Even if we have name it will be faster to access it using address. Because every time you call it by name it will be converted to an logical address then to a physical address , any way you need to address to access the element. So sometimes it convenient to use address rather than name. If we use the name it will be faster to us (as humans) to read it , but for machine address is easier to implement.
And here pointers came into picture , pointers generally handle the addresses and when ever you are writing any program or any entity with pointers it is supposed to run faster than programs that doesn't have pointers. This is the first advantage and the other advantage is that dynamically created Data structures you can not rename them when you create them , so in this case the only way to access them is by address. So pointers are going to play a critical role in C programming language.
So let's talk about pointer. What is pointer? the pointer is a variable which supposed to contain the address of some other variable or some other entity. Every system will have some bits to address the memory and the pointer should hold that number of bits. So the size of a pointer again it will depend on the platform , some platforms will have 16 bit in others 32 bit .
char a;
int b;
int *p; //can be read as *p is a variable of type int .
p = &b;
we have 2 operators 1 is = and other is & , as we see in first part operator & have highest president , this operator is used to get the address of b , you can the addresses of variable or the other Data structures only which they are declared in the memory. For example you can't get the address of expression or constants.
let's say the addresses in this platform is 8 bits = 1 byte , so the pointer will take only 1 byte.
let's say the starting address of b is 100 , even if b is 2 bytes , p still can be 1 byte because it is pointing to address , and hence &b is 100 then value of p is 100. Even if the size is int or char or any other structure the size of pointer will always be same.
We have houses with different sizes but every house have same address.
So remember this what ever the data structure is the address will always be the same , you should not chain the size of pointer with the size of element.
But you can think that wait if the size doesn't matter so why I use int *p can't I use char by example? it's true , but there is a problem called address arithmetic will talk about it later.
now if I use p It's as I am standing at 100 , and if I need to access inside the address I need to use *p and here it called differencing or indirection operation.
& : address
* : dereferencing and indirection
let's think about it as this if I am using p (or &b) alone I am gonna be the violet person , if I am using the star (or b) I am inside of it (blue guy).
so now *p and b are the same.
Examples
# include <stdio.h>
int main()
{
int x = 5;
int *y = &x; // assume address of x is 0x7ffe5b67bf54
printf("%d\n" , x); // output : 5
printf("%p\n" , &x); // output : 0x7ffe5b67bf54
printf("%p\n" , y); // output : 0x7ffe5b67bf54
printf("%d\n" , *y); // output : 5
printf("%p\n" , &y); // output : 0x7ffe5b67bf54
return 0;
}
another example but with function
void f(int *p , int *q)
{
p = q;
*p = 2;
}
int i = 0 , j = 1;
int main() {
f(&i , &j);
printf("%d %d\n",i,j);
return 0;
}
this is the visualize before the function start to execute. p pointing to &i and q pointing to &j
after execute both will be pointing to the &j , then the program will print 0 2
Pointers and functions
void swap(int x , int y) {
int temp;
temp = x;
x = y;
y = temp;
}
int main() {
int a,b;
a = 3;
b = 4;
swap(a,b);
return 0;
}
in the main function we have a and b , they will on the stack of main
and when I pass them by value into the function a new stack for the function will be created
x and y here are different than a and b , because they take only the value of a and b , but not their addresses so they will be occupied with different address in memory. So any change to x or y it will not affect a and b.
let's see this version now
void swap(int *x , int *y) {
int temp;
temp = *x; // temp = 3
*x = *y; // *x=a=4
*y = temp; // *y=b=temp=3
}
int main() {
int a,b;
a = 3;
b = 4;
swap(&a,&b); // assume &a=100 and &b=200
return 0;
}
now x have value of the address 'a' and y have value of address 'b' , so *x is 'a' and *y is 'b' . X and Y will still have different addresses than a and b but value of X and Y are the address of 'a' and 'b'.
so any change I make for *x it will affect 'a' since I am modifying an address here , same for *y.
This is a way to access a and b from a function.
Pointers and arrays
Arrays and pointers are strongly related in sense , what ever you can do with arrays and suffix you can do it with pointers.
let's say we have a[5] it means we have 5 elements of int , so it will allocate 5 block of contiguous memory space , let's say that our block start from 100 and on the system every integer will take 2 bytes and the system is byte addressable it means every byte will get different address , general systems are word addressable it mean a word will get unique address , so if word is 2 byte it means that every 2 byte have an unique address. But we assume it 1 byte so every one byte will get unique address.
so our array need 10 bytes (2 * 5)
array name is not a variable , it is just an another way to represent the address. So in memory no name will be allocated. But a[0] a[1] ... they will be allocated but not a it self. a is like standing at outside the cell of 100 , *a it mean you go inside the cell so *a=a[0] , now if I need to go to a[1].
so now we will think that standing at *(a+2)=a[1] but it is wrong , here we will have something called address arithmetic. address arithmetic work this way so whenever try to add integer to either array name of pointer the addition will be scale addition.
So if 'a' represent 100 here so a+1 will represent the next element of a which 102 , a+2 is 104 , so when I write in general a+i it will convert internally to a+(i*sizeof(int)) so I don't care about the type of element so *(a+3) = a[3] so whenever i type a[3] it will be converted internally to *(a+3) , a[3] is human friendly , *(a+3) is machine friendly. Also (a+3)=&a[3] no need to put it like &(a[3]) because [] have higher presentation than &.
if I declare a pointer *p
int *p;
p = &a[0]; // p will point to address 100
p = p+1; // p will point to address 102
p = p+1; // p will point to address 104
how does the compiler know that? remember before when we say every pointer have a fixed size whenever the type of what is pointing to? that's why when I type int before pointer the compiler will now increment it properly.
p have address 100 so when i type p=p+3 it will be equal 106 and
*(p+3)=a[3]=p[3]
And
p+3=a+3=&a[3]
if I have a as an array name
a=a+1; // X
a++; // X
a=b; //X
all of those are not allowed because a is not a value. But in other have those are valid
p=p+1;
p++;
p=variable;
because p is a variable , this is the difference between array and pointer
So whenever I pass the name of array to function I am passing the starting address , so this will take an pointer in the function that we are calling , and using that pointer it can do the same operations that can be performed on the array.
Pointer arithmetic or address arithmetic
Valid pointers operations:
a) assignment of pointers of the same type :
if 2 pointers have same type we can assign them eg :
int *p,*q; // same type
p=q; // legal
q=p; // legal
int *p , a[];
p=a;// allowed
a=p;// Now allowed hince a is not an variable
if we need to assign 2 pointers of different type we need to go threw type casting. We say that all pointers have same size of memory it is right , we can assign them but when we need to do arithmetic operation on them the compiler will get confused.
char *c;
int *p;
p = (int*)c;
you should always casting it , and it should be wild pointer (not been initialized to anything even Null) otherwise is not allowed.
b) adding or subtracting a pointer by an int :
you should not have a problem in adding and subtracting , unless you go out of the bounce . you will get an error.
c) subtracting or comparing two pointers to members of the same array :
if we have 2 pointers pointing to the same array you can compare them < , != ... Also you can subtract them only , you can't shift them or adding them extra. Let's say we need to get number of elements between p and q including p and q , so I will write q-p+1 between p and q , if every cell is 2 byte p-q will be divided by 2 , let's say p start at 102 and q at 106 , so p-q = 4/2 = 2 , so q-p+1=2+1=3 , which is right we have 3 cells so 3 elements.
d) assign or comparing to zero
you can only assign pointer to zero but it's meaningless. The only case it's if we need to make sure that a pointer is valid or not we generally if it's 0 or not.