C pointers and memory - c

I'm learning C and now I hit a wall. Its difficult for me to understand pointers.
Imagine I have this code:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define DELTA 33
int calls, seed=356;
int sum_ds(int a){
int d=DELTA;
calls++;
return a+d+seed;
}
int main() {
int num;
int *ptr;
int **handle;
num = 14;
ptr = (int *)malloc(2 * sizeof(int));
handle = &ptr;
*(*handle+0) = num;
*(*handle+1) = num+1;
*ptr = num-2;
ptr = &num;
*ptr = sum_ds(num-2);
}
Lets go step by step trough my understanding.
1 - int calls creates a variable named calls and doesn't initializes it so it contains rubbish. It is stored on DATA and let's say with the memory address 0xFFAA.
2 - int seeds creates a variable named seeds initialized with the integer 356. It is stored on DATA and let's say with the memory address 0xFFAB.
3 - int num creates a variable named num and doesn't initializes it so it contains rubbish. It is stored on the STACK and let's say with the memory address 0xFFAC.
4 - int *ptr creates a pointer to int and does not assign any address to it. It is stored on the STACK and let's say with the memory address 0xFFAD.
5 - int **handle creates a pointer to a pointer of int and does not assign any address to it. It is stored on the STACK and let's say with the memory address 0xFFAE. (MANY DOUBTS HERE)
6 - num = 14 goes to the address 0xFFAC and stores the number 14 on it. It's done in the STACK.
7 - ptr = (int *)malloc(2 * sizeof(int)) On the HEAP it's assigned memory size for 2 ints and the address of the first memory byte (let's say 0xFFZZ) is stored (on STACK) on ptr so now *ptr points to that memory address.
8 - handle = &ptr handle now points to ptr. I believe it now points to whatever is on 0xFFZZ (MANY DOUBTS HERE)
9 - *(*handle+0) = num the pointer to the pointer of int now its assigned with the value of num (14) (MANY MANY MANY MANY DOUBTS HERE)
10 - *(*handle+1) = num+1 the pointer of pointer plus one of int now its assigned with the value of num + 1 (15) (MANY MANY MANY MANY DOUBTS HERE)
11 - *ptr = num-2 the value point by ptr it's assigned with the value of num - 2 (12). I believe it goes to the memory address 0xFFZZ and stores there the number 12.
12 - ptr = &num ptr now points to num, i believe it now points to 0xFFAC.
13 - *ptr = sum_ds(num-2) the value pointed by ptr is the returned value of sum_ds. I belive 0xFFAC it's assigned with 401 (12+33+356)
Is this right?

1 - int calls creates a variable named calls and doesn't initializes it so it contains rubbish. It is stored on DATA and let's say with the memory address 0xFFAA.
2 - int seeds creates a variable named seeds initialized with the integer 356. It is stored on DATA and let's say with the memory address 0xFFAB.
One little detail: sizeof(int) is greater than 1 (it is 4 on most mainstream platforms, so the 2nd address could not be 1 higher than the 1st. Other than that, AFAIK you are correct so far.
3 - int num creates a variable named num and doesn't initializes it so it contains rubbish. It is stored on the STACK and let's say with the memory address 0xFFAC.
4 - int *ptr creates a pointer to int and does not assign any address to it. It is stored on the STACK and let's say with the memory address 0xFFAD.
Another little detail: on most mainstream platforms, the stack grows downward, so the 4th address would be less than the 3rd. Other than that, AFAIK you are correct so far. (Moreover, addresses on the data segment, the heap and the stack would be rather different in real life.)
7 - ptr = (int *)malloc(2 * sizeof(int)) On the HEAP it's assigned memory size for 2 ints and the address of the first memory byte (let's say 0xFFZZ) is stored (on STACK) on ptr so now *ptr points to that memory address.
To be nitpicky, 'Z' is not a hexadecimal number :-) So let's say it is 0x1000 instead.
8 - handle = &ptr handle now points to ptr. I believe it now points to whatever is on 0xFFZZ (MANY DOUBTS HERE)
No, handle now contains the address of ptr, that is 0xFFAD. Indirectly though - through ptr - it indeed points to 0x1000 (was 0xFFZZ in your example).
9 - *(*handle+0) = num the pointer to the pointer of int now its assigned with the value of num (14) (MANY MANY MANY MANY DOUBTS HERE)
Basically correct. The notation you use is not the easiest to deal with, which makes it more difficult for you to follow what's going on. After step 8, *handle is equivalent to ptr. And due to pointers and arrays being interchangeable in many common situations, *(ptr+0) is equivalent to ptr[0], and also to *ptr.
10 - *(*handle+1) = num+1 the pointer of pointer plus one of int now its assigned with the value of num + 1 (15) (MANY MANY MANY MANY DOUBTS HERE)
Similar to the previous point, you are in effect assigning ptr[1] = num+1. Keep in mind though that ptr is int*, so the address difference between ptr and ptr + 1 equals to sizeof(int), which is, as mentioned above, usually 4.
11 - *ptr = num-2 the value point by ptr it's assigned with the value of num - 2 (12). I believe it goes to the memory address 0xFFZZ and stores there the number 12.
Yes, this overwrites the value set in step 9.
12 - ptr = &num ptr now points to num, i believe it now points to 0xFFAC.
Correct.
13 - *ptr = sum_ds(num-2) the value pointed by ptr is the returned value of sum_ds. I belive 0xFFAC it's assigned with 401 (12+33+356)
Correct. Since the previous step made *ptr equivalent to num, this call is also equivalent to num = sum_ds(num-2).

Since calls is outside any function, it's a static variable. Static variables are initialized to 0.
Since num is a local variable (auto storage class) it is not initialized.
At your point 9, *(*handle+0) = num; is probably easiest to decipher by keeping in mind that handle = &ptr, therefore *handle = ptr, so this is basically equivalent to *(ptr+0) = num;, which is (in turn) equivalent to ptr[0] = num;.
For point 10, you get pretty much the same thing, except a +1 in both cases, so it's saying ptr[1] = num+1;.
For point 11, *ptr=num-2; overwrites what was written in point 9 -- i.e., *ptr is the same as *(ptr+0), so this is equivalent to ptr[0] = num-2;
You're correct in point 12 that ptr has been set to point at num. That means in point 13, the assignment is equivalent to num=sum_ds(num-2);

A variable has an address and stores at that address are the value that you just put:
int a = 10;
Right?
A pointer is a kind of variable that stores the address of another variable.
So...
int a = 10;
int *p = &a;
This means that "p" stores the address of "a" that has the value that you want to use.
Execute this code below and you'll understand:
printf("%p %p %d %d\n", p, &a, *p, a);

Related

Adding the integer to hexadecimal address and How is the pointers calculation done in C? [duplicate]

#include<stdio.h>
int main(void){
int *ptr,a,b;
a = ptr;
b = ptr + 1;
printf("the vale of a,b is %x and %x respectively",a,b);
int c,d;
c = 0xff;
d = c + 1;
printf("the value of c d are %x and %x respectively",c,d);
return 0;
}
the out put value is
the vale of a,b is 57550c90 and 57550c94 respectively
the value of c d are ff and 100 respectively%
it turns out the ptr + 1 actually, why it behave this way?
Because pointers are designed to be compatible with arrays:
*(pointer + offset)
is equivalent to
pointer[offset]
So pointer aritmetic doesn't work in terms of bytes, but in terms of sizeof(pointer base type)-bytes sized blocks.
Consider what a pointer is... it's a memory address. Every byte in memory has an address. So, if you have an int that's 4 bytes and its address is 1000, 1001 is actually the 2nd byte of that int and 1002 is the third byte and 1003 is the fourth. Since the size of an int might vary from compiler to compiler, it is imperative that when you increment your pointer you don't get the address of some middle point in the int. So, the job of figuring out how many bytes to skip, based on your data type, is handled for you and you can just use whatever value you get and not worry about it.
As Basile Starynkvitch points out, this amount will vary depending on the sizeof property of the data member pointed to. It's very easy to forget that even though addresses are sequential, the pointers of your objects need to take into account the actual memory space required to house those objects.
Pointer arithmetic is a tricky subject. A pointer addition means passing to some next pointed element. So the address is incremented by the sizeof the pointed element.
Short answer
The address of the pointer will be incremented by sizeof(T) where T is the type pointed to. So for an int, the pointer will be incremented by sizeof(int).
Why?
Well first and foremost, the standard requires it. The reason this behaviour is useful (other than for compatibility with C) is because when you have a data structure which uses contiguous memory, like an array or an std::vector, you can move to the next item in the array by simply adding one to the pointer. If you want to move to the nth item in the container, you just add n.
Being able to write firstAddress + 2 is far simpler than firstAddress + (sizeof(T) * 2), and helps prevent bugs arising from developers assuming sizeof(int) is 4 (it might not be) and writing code like firstAddress + (4 * 2).
In fact, when you say myArray[4], you're saying myArray + 4. This is the reason that arrays indices start at 0; you just add 0 to get the first element (i.e. myArray points to the first element of the array) and n to get the nth.
What if I want to move one byte at a time?
sizeof(char) is guaranteed to be one byte in size, so you can use a char* if you really want to move one byte at a time.
A pointer is used to point to a specific byte of memory marking where an object has been allocated (technically it can point anywhere, but that's how it's used). When you do pointer arithmetic, it operates based on the size of the objects pointed to. In your case, it's a pointer to integers, which have a size of 4 bytes each.
Let consider a pointer p. The expression p+n is like (unsigned char *)p + n * sizeof *p (because sizeof(unsigned char) == 1).
Try this :
#include <stdio.h>
#define N 3
int
main(void)
{
int i;
int *p = &i;
printf("%p\n", (void *)p);
printf("%p\n", (void *)(p + N));
printf("%p\n", (void *)((unsigned char *)p + N * sizeof *p));
return 0;
}

In C, why does incrementing a pointer adds the size of the type the pointer is referring to instead of 1? [duplicate]

#include<stdio.h>
int main(void){
int *ptr,a,b;
a = ptr;
b = ptr + 1;
printf("the vale of a,b is %x and %x respectively",a,b);
int c,d;
c = 0xff;
d = c + 1;
printf("the value of c d are %x and %x respectively",c,d);
return 0;
}
the out put value is
the vale of a,b is 57550c90 and 57550c94 respectively
the value of c d are ff and 100 respectively%
it turns out the ptr + 1 actually, why it behave this way?
Because pointers are designed to be compatible with arrays:
*(pointer + offset)
is equivalent to
pointer[offset]
So pointer aritmetic doesn't work in terms of bytes, but in terms of sizeof(pointer base type)-bytes sized blocks.
Consider what a pointer is... it's a memory address. Every byte in memory has an address. So, if you have an int that's 4 bytes and its address is 1000, 1001 is actually the 2nd byte of that int and 1002 is the third byte and 1003 is the fourth. Since the size of an int might vary from compiler to compiler, it is imperative that when you increment your pointer you don't get the address of some middle point in the int. So, the job of figuring out how many bytes to skip, based on your data type, is handled for you and you can just use whatever value you get and not worry about it.
As Basile Starynkvitch points out, this amount will vary depending on the sizeof property of the data member pointed to. It's very easy to forget that even though addresses are sequential, the pointers of your objects need to take into account the actual memory space required to house those objects.
Pointer arithmetic is a tricky subject. A pointer addition means passing to some next pointed element. So the address is incremented by the sizeof the pointed element.
Short answer
The address of the pointer will be incremented by sizeof(T) where T is the type pointed to. So for an int, the pointer will be incremented by sizeof(int).
Why?
Well first and foremost, the standard requires it. The reason this behaviour is useful (other than for compatibility with C) is because when you have a data structure which uses contiguous memory, like an array or an std::vector, you can move to the next item in the array by simply adding one to the pointer. If you want to move to the nth item in the container, you just add n.
Being able to write firstAddress + 2 is far simpler than firstAddress + (sizeof(T) * 2), and helps prevent bugs arising from developers assuming sizeof(int) is 4 (it might not be) and writing code like firstAddress + (4 * 2).
In fact, when you say myArray[4], you're saying myArray + 4. This is the reason that arrays indices start at 0; you just add 0 to get the first element (i.e. myArray points to the first element of the array) and n to get the nth.
What if I want to move one byte at a time?
sizeof(char) is guaranteed to be one byte in size, so you can use a char* if you really want to move one byte at a time.
A pointer is used to point to a specific byte of memory marking where an object has been allocated (technically it can point anywhere, but that's how it's used). When you do pointer arithmetic, it operates based on the size of the objects pointed to. In your case, it's a pointer to integers, which have a size of 4 bytes each.
Let consider a pointer p. The expression p+n is like (unsigned char *)p + n * sizeof *p (because sizeof(unsigned char) == 1).
Try this :
#include <stdio.h>
#define N 3
int
main(void)
{
int i;
int *p = &i;
printf("%p\n", (void *)p);
printf("%p\n", (void *)(p + N));
printf("%p\n", (void *)((unsigned char *)p + N * sizeof *p));
return 0;
}

Tricky C exercise related to pointers. Need help in understanding the solution

Consider the following code:
int *foo (int x) {
int a[2];
a[0] = x;
a[1] = x + 1;
return a;
}
…
int *p1 = foo(2);
int *p2 = foo(3);
At the conclusion of this code snippet, what are the values of each of the following? (answer is given)
p1[0] = 3
p1[1] = 4
p2[0] = 3
p2[1] = 4
Since a is allocated on the stack, the memory does not remain allocated when the call returns and may be reused by something else. In this case, since foo(3) is called immediately after foo(2) at the same call depth (i.e. they use the same stack space), they will both return the same pointer– i.e. p1 is equal to p.
I did not understand the above explanation. What does it really mean? Why we have exact same values for p2 and p1? I know that you cannot return a pointer to a local variable in C..but I do not understand why p2 and p1 has same values ....
Using a pointer to automatic storage variables (variables scoped to functions) outside its scope is undefined, which is exactly why you should never do it, and never depend on its behavior.
However, if you want to peel back the cover on the compiler/machine/os, the reason is that the automatic storage happens to be allocated at the same address for the two function calls.
An example...
#include "stdio.h"
int* foo(int x) {
int a[2];
printf("&a[0] = %p\n", &a[0]);
printf("&a[1] = %p\n\n", &a[1]);
a[0] = x;
a[1] = x + 1;
return a;
}
int main(int argc, char* argv[]) {
printf("foo(2)\n");
int* p1 = foo(2);
printf("foo(3)\n");
int* p2 = foo(3);
printf("p1[0] = %i\n", p1[0]);
printf("p1[1] = %i\n\n", p1[1]);
printf("p2[0] = %i\n", p2[0]);
printf("p2[1] = %i\n", p2[1]);
return 0;
}
outputs...
foo(2)
&a[0] = 0x7fff4dd0f054
&a[1] = 0x7fff4dd0f058
foo(3)
&a[0] = 0x7fff4dd0f054
&a[1] = 0x7fff4dd0f058
p1[0] = 3
p1[1] = 4
p2[0] = 3
p2[1] = 4
So, &a[0] and &a[1] have the same addresses in both foo(2) and foo(3).
What's happening is that when you enter a function, it generally creates a stack frame (by decrementing the stack pointer on x86). This allocates memory on the stack for automatic storage variables. When you leave the function, the stack frame is destroyed (and the stack pointer returned to its original value). So if you enter the same function again, you generally use the same memory for the stack frame (stack pointer is decremented to same value as last call to function).
it is undefined behavior, your program might crash too.
The local variable is stored on the stack and its life time is only with in the scope of the function.
In your case, the program is reusing the same location the next time , so your values are overwritten, but it is not always going to be same and never return address of local variable.
After each function call memory is allocated on stack for function variables and stack pointer is moved forward. After function execution stack memory is not erased for efficiency reasons and all data stays there. So if you call same function second time and leave some variables uninitialized you can find some funny values from last function call. Every array in C is stored as a big chunk of memory where elements are found by shifting pointer.
As for you question: foo returns pointer to integer, which is actually a memory address.
After foo(2) p1 will store some address for example 0x00. Adding braces [] with index to a integer pointer means to add an integer size * index to a memory address. We may add any random index to pointer and try to get data from there. If we get lucky and memory is readable - we will have some garbage data. After first function call
p1 points to a stack array and values are:
p1[0] == 2;
p2[1] == 3;
p1 == 0x00; (for example)
Function is executed and it returns stack pointer back. Next function call foo(3) gets same memory chunk on stack. Second call rewrites variables with new values. After second call we get:
p2[0] == 3;
p2[1] == 4;
p2 == 0x00; (same memory address)
The problem is that p1 points to same memory address on stack. If you call any other function - both p1 and p2 will be changed again, because same region of stack will be reused again.
Stack unwind while returning from the function call will release the memory allocated for the array. That memory is no longer available. The behavior is undefined.
In your function foo() you have a local variable and your are trying to return the address of the local variable. Don't your compiler throw the below error:
warning: function returns address of local variable
If you have to get the answer you have posted then you need to make the array int a[2] global.
Else we have an undefined behavior here.

Incrementing pointer to array

I came across this program on HSW:
int *p;
int i;
p = (int *)malloc(sizeof(int[10]));
for (i=0; i<10; i++)
*(p+i) = 0;
free(p);
I don't understand the loop fully.
Assuming the memory is byte addressable, and each integer takes up 4 bytes of memory, and say we allocate 40 bytes of memory to the pointer p from address 0 to 39.
Now, from what I understand, the pointer p initially contains value 0, i.e. the address of first memory location. In the loop, a displacement is added to the pointer to access the subsequent integers.
I cannot understand how the memory addresses uptil 39 are accessed with a displacement value of only 0 to 9. I checked and found that the pointer is incremented in multiples of 4. How does this happen? I'm guessing it's because of the integer type pointer, and each pointer is supposedly incremented by the size of it's datatype. Is this true?
But what if I actually want to point to memory location 2 using an integer pointer. So, I do this: p = 2. Then, when I try to de-reference this pointer, should I expect a segmentation fault?
Now, from what I understand, the pointer p initially contains value 0
No, the pointer p would not hold the value 0 in case malloc returns successfully.
At the point of declaring it, the pointer is uninitialized and most probably holds a garbage value. Once you assign it to the pointer returned by malloc, the pointer points to a region of dynamically allocated memory that the allocator sees as unoccupied.
I cannot understand how the memory addresses uptil 39 are accessed
with a displacement value of only 0 to 9
The actual displacement values are 0, 4, 8, 12 ... 36. Because the pointer p has a type, in that case int *, this indicates that the applied offset in pointer arithmetics is sizeof(int), in your case 4. In other words, the displacement multiplier is always based on the size of the type that your pointer points to.
But what if I actually want to point to memory location 2 using an
integer pointer. So, I do this: p = 2. Then, when I try to
de-reference this pointer, should I expect a segmentation fault?
The exact location 2 will most probably be unavailable in the address space of your process because that part would either be reserved by the operating system, or will be protected in another form. So in that sense, yes, you will get a segmentation fault.
The general problem, however, with accessing a data type at locations not evenly divisible by its size is breaking the alignment requirements. Many architectures would insist that ints are accessed on a 4-byte boundary, and in that case your code will trigger an unaligned memory access which is technically undefined behaviour.
Now, from what I understand, the pointer p initially contains value 0
No, it contains the address to the first integer in an array of 10. (Assuming that malloc was successful.)
In the loop, a displacement is added to the pointer to access the subsequent integers.
Umm no. I'm not sure what you mean but that is not what the code does.
I checked and found that the pointer is incremented in multiples of 4. How does this happen?
Pointer arithmetic, that is using + - ++ -- etc operators on a pointer, are smart enough to know the type. If you have an int pointer a write p++, then the address that is stored in p will get increased by sizeof(int) bytes.
But what if I actually want to point to memory location 2 using an integer pointer. So, I do this: p = 2.
No, don't do that, it doesn't make any sense. It sets the pointer to point at address 0x00000002 in memory.
Explanation of the code:
int *p; is a pointer to integer. By writing *p = something you change the contents of what p points to. By writing p = something you change the address of where p points.
p = (int *)malloc(sizeof(int[10])); was written by a confused programmer. It doesn't make any sense to cast the result of malloc in, you can find extensive information about that topic on this site.
Writing sizeof(int[10]) is the same as writing 10*sizeof(int).
*(p+i) = 0; is the very same as writing p[i] = 0;
I would fix the code as follows:
int *p = malloc(sizeof(int[10]));
if(p == NULL) { /* error handling */ }
for (int i=0; i<10; i++)
{
p[i] = 0;
}
free(p);
Since you have a typed pointer, when you perform common operations on it (addition or subtraction), it automatically adjusts the alignment for your type. Here, since on your computer sizeof (int) is 4, p + i will result in the address p + sizeof (int) * i, or p + 4*i in your case.
And you seem to misunderstand the statement *(p+i) = 0. This statement is equivalent to p[i] = 0. Obviously, your malloc() call won't return you 0, except if it fails to actually allocate the memory you asked.
Then, I assume that your last question means "If I shift my malloc-ated address by exactly two bytes, what will occur?".
The answer depends on what you do next and on the endianness of your system. For example:
/*
* Suppose our pointer p is well declared
* And points towards a zeroed 40 bytes area.
* (here, I assume sizeof (int) = 4)
*/
int *p1 = (int *)((char *)p + 2);
*p1 = 0x01020304;
printf("p[0] = %x, p[1] = %x.\n", p[0], p[1]);
Will output
p[0] = 102, p[1] = 3040000.
On a big endian system, and
p[0] = 3040000, p[1] = 102
On a little endian system.
EDIT : To answer to your comment, if you try to dereference a randomly assigned pointer, here is what can happen:
You are lucky : the address you type correspond to a memory area which has been allocated for your program. Thus, it is a valid virtual address. You won't get a segfault, but if you modify it, it might corrupt the behavior of your program (and it surely will ...)
You are luckier : the address is invalid, you get a nice segfault that prevents your program from totally screwing things up.
It is called pointer arithmetic. Add an integer n to a pointer of type t* moves the pointer by n * sizeof(t) elements. Therefore, if sizeof(int) is 4 bytes:
p + 1 (C) == p + 1 * sizeof(int) == p + 1 * 4 == p + 4
Then it is easier to index your array:
*(p+i) is the i-th integer in the array p.
I don't know if by "memory location 2" you mean your example memory address 2 or if you mean the 2nd value in your array. If you mean the 2nd value, that would be memory address 1. To get a pointer to this location you would do int *ptr = &p[1]; or equivalently int *ptr = p + 1;, then you can print this value with printf("%d\n", *ptr);. If you mean the memory address 2 (your example address), that would be the 3rd value in the array, then you'd want p[2] or p + 2. Note that memory addresses are usually in hex and wouldn't actually start at 0. It would be something like 0x092ef000, 0x092ef004, 0x092ef008, . . .. All of the other answers aren't understanding that you are using memory addresses 0 . . . 39 just as example addresses. I don't think you honestly are referring to the physical locations starting at address 0x00000000 and if you are then what everyone else is saying is right.

Why a pointer + 1 add 4 actually

#include<stdio.h>
int main(void){
int *ptr,a,b;
a = ptr;
b = ptr + 1;
printf("the vale of a,b is %x and %x respectively",a,b);
int c,d;
c = 0xff;
d = c + 1;
printf("the value of c d are %x and %x respectively",c,d);
return 0;
}
the out put value is
the vale of a,b is 57550c90 and 57550c94 respectively
the value of c d are ff and 100 respectively%
it turns out the ptr + 1 actually, why it behave this way?
Because pointers are designed to be compatible with arrays:
*(pointer + offset)
is equivalent to
pointer[offset]
So pointer aritmetic doesn't work in terms of bytes, but in terms of sizeof(pointer base type)-bytes sized blocks.
Consider what a pointer is... it's a memory address. Every byte in memory has an address. So, if you have an int that's 4 bytes and its address is 1000, 1001 is actually the 2nd byte of that int and 1002 is the third byte and 1003 is the fourth. Since the size of an int might vary from compiler to compiler, it is imperative that when you increment your pointer you don't get the address of some middle point in the int. So, the job of figuring out how many bytes to skip, based on your data type, is handled for you and you can just use whatever value you get and not worry about it.
As Basile Starynkvitch points out, this amount will vary depending on the sizeof property of the data member pointed to. It's very easy to forget that even though addresses are sequential, the pointers of your objects need to take into account the actual memory space required to house those objects.
Pointer arithmetic is a tricky subject. A pointer addition means passing to some next pointed element. So the address is incremented by the sizeof the pointed element.
Short answer
The address of the pointer will be incremented by sizeof(T) where T is the type pointed to. So for an int, the pointer will be incremented by sizeof(int).
Why?
Well first and foremost, the standard requires it. The reason this behaviour is useful (other than for compatibility with C) is because when you have a data structure which uses contiguous memory, like an array or an std::vector, you can move to the next item in the array by simply adding one to the pointer. If you want to move to the nth item in the container, you just add n.
Being able to write firstAddress + 2 is far simpler than firstAddress + (sizeof(T) * 2), and helps prevent bugs arising from developers assuming sizeof(int) is 4 (it might not be) and writing code like firstAddress + (4 * 2).
In fact, when you say myArray[4], you're saying myArray + 4. This is the reason that arrays indices start at 0; you just add 0 to get the first element (i.e. myArray points to the first element of the array) and n to get the nth.
What if I want to move one byte at a time?
sizeof(char) is guaranteed to be one byte in size, so you can use a char* if you really want to move one byte at a time.
A pointer is used to point to a specific byte of memory marking where an object has been allocated (technically it can point anywhere, but that's how it's used). When you do pointer arithmetic, it operates based on the size of the objects pointed to. In your case, it's a pointer to integers, which have a size of 4 bytes each.
Let consider a pointer p. The expression p+n is like (unsigned char *)p + n * sizeof *p (because sizeof(unsigned char) == 1).
Try this :
#include <stdio.h>
#define N 3
int
main(void)
{
int i;
int *p = &i;
printf("%p\n", (void *)p);
printf("%p\n", (void *)(p + N));
printf("%p\n", (void *)((unsigned char *)p + N * sizeof *p));
return 0;
}

Resources