Created
May 12, 2017 03:59
-
-
Save gbezyuk/44362ee05bf0d5d20733af1e9b756f8e to your computer and use it in GitHub Desktop.
C language addresses, arrays and pointers explained in details.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
/*int part1 () { | |
int var = 15; | |
printf("var: value is %d, address is %p\n", var, &var); | |
// changing var directly | |
var = 20; | |
printf("var value was changed directly and now is %d, but the address is still %p\n", var, &var); | |
*(&var) = 30; | |
printf("var value was changed indirectly and now is %d, but the address is still %p\n", var, &var); | |
// now let's intruduce a pointer to our variable | |
int *p = &var; | |
printf("we've got a pointer p now, which stores var's address. p = %p, &var = %p, obviously the same thing\n", p, &var); | |
printf("Both via the variable name and using the pointer we can get or modify the data itself. Let's read first\n"); | |
printf("*p = %d; var = %d\n", *p, var); | |
// now we modify using pointer | |
*p = 40; | |
printf("after modification: *p = %d; var = %d\n", *p, var); | |
printf("we use * sign in order to access the data located on the address stored in the pointer\n"); | |
printf("direct assignment like p = 40 will change the address stored, but will not affect the data at this address.\n"); | |
printf("furthermore, trying to access the data from a wrong address may result in an error like a segmentation fault"); | |
p = 40; | |
printf("It will fail, I guess: %p => %d", p, *p); | |
return 0; | |
}*/ | |
void print_array (int *a, int array_length) { | |
int i; | |
printf("Array of size %d starting at %p printed with indexing: [ ", array_length, a); | |
for (i = 0; i < array_length - 1; i++) { | |
printf("%d, ", a[i]); | |
} | |
printf("%d ]\n", a[i]); | |
} | |
void print_array_alternative (int *a, int array_length) { | |
int i; | |
printf("Array of size %d starting at %p printed with pointers: [ ", array_length, a); | |
for (i = 0; i < array_length - 1; i++) { | |
printf("%d, ", *(a + i)); // here's the only difference. not that there is no sizeof operator | |
} | |
printf("%d ]\n", *(a + i)); // here's the only difference. not that there is no sizeof operator | |
} | |
int main () { | |
int i; | |
const int L = 5; | |
int a[L] = { 100, 105, 110, 115, 120 }; | |
printf("We've just created an array of integers\n"); | |
print_array(a, L); | |
print_array(a, L); | |
printf("\nNow a bit of weirdness with `a`:\n"); | |
printf("a == %p, a stores the address of the beginning of array\n", a); | |
printf("&a == %p, weird enough, it's own address is the same\n", &a); | |
printf("*&a == %p, so if we get the value at this address we receive this address... wait, WAT?\n", *&a); | |
printf("*a == %d, but when we don't mess with an extra `&`, we get the first element, okay...\n", *a); | |
printf("Yep, the array name behaves like a pointer to itself. " | |
"I guess it's because the memory is allocated right here while compiling the program, not somewhere else in the heap dynamically.\n\n"); | |
printf("Let's compare it with `*a` and `a[0]`:\n"); | |
printf("*a == %d, `*a` means here `read a single integer variable at the address of a`\n", *a); | |
printf("a[0] == %d, with `a[0]` being more or less a syntax sugar for the `*(a + 0)` expression\n", a[0]); | |
printf("*(a + 0) == %d` — zero shouldn't change a thing, right?\n", *(a + 0)); | |
printf("&*a == %p, here we first get the first variable in the array, then it's address, which is exactly the address of array itself\n", &*a); | |
printf("&a[0] == %p, same but using indexation instead of `*`\n", &a[0]); | |
printf("&*(a + 0) == %p, once again with a pointer, the parallel with indexation made more obvious\n", & * (a + 0)); | |
printf("*&a[0] == %d\n", *&a[0]); | |
printf("\nNow let's repeat everything for the `1` index:\n"); | |
printf("a[1] == %d, with `a[1]` being more or less a syntax sugar for the `*(a + 1)` expression\n", a[1]); | |
printf("*(a + 1) == %d` — proving the previous line's claim\n", *(a + 1)); | |
printf("&a[1] == %p, the address not of the array as a whole, but of its `1`st element. LOOK CAREFULLY HERE!\n", &a[1]); | |
printf("&*(a + 1) == %p, same thing now done with a pointer\n", & * (a + 1)); | |
printf("*&a[1] == %d\n", *&a[1]); | |
printf("\nOK, not it's time to contemplate the nature of indexation as a syntax sugar:\n"); | |
printf("&a[0] == %p, &a[1] == %p, &a[1] - &a[0] == %ld; and sizeof(int) == %ld\n", | |
&a[0], &a[1], &a[1] - &a[0], sizeof(int)); | |
printf("Wait, how on Earth could be te substraction that wrong? I mean, %p minus %p should be 4, why is it %ld?\n", | |
&a[0], &a[1], &a[1] - &a[0]); | |
printf("Well, it's because the pointer we use is a `typed` one, so it gives us the answer in terms of `how many vars will fit?`, not in terms of `how many bytes are there?`"); | |
printf("Wanna proof? Just convert to `untyped` pointers, using `(void *)` for conversion:\n"); | |
printf("(void*)&a[0] == %p, (void*)&a[1] == %p, (void*)&a[1] - (void*)&a[0] == %ld; and sizeof(int) is still %ld\n", | |
(void*)&a[0], (void*)&a[1], (void*)&a[1] - (void*)&a[0], sizeof(int)); | |
printf("\nAlright, now it starts to make sense. So, with `void*`, can we read somewhere in between our nicely distributed integers? Let's check!\n"); | |
printf("We'll build our expression step by step, starting with the a == %p\n", a); | |
printf("Now (void*)a == %p, no changes\n", (void*)a); | |
printf("Now (void*)(a + 0) == %p, once again no changes\n", (void*)(a + 0)); | |
printf("But compare ((void*)a) + 1 == %p, and just *(a + 1) = %p\n", ((void*)a) + 1, a + 1); | |
printf("To make the results same, we'll need to use sizeof: ((void*)a) + 1 * sizeof(int) == %p, and just *(a + 1) = %p\n", ((void*)a) + 1 * sizeof(int), a + 1); | |
printf("So, actually this difference would allow us to hack our array a little and to read in between:\n"); | |
for (i = 0; i <= (L - 1) * sizeof(int); i++) { | |
printf("\t ((void*)a) + %d == %p; *(int*)(((void*)a) + %d) == %d\n", i, ((void*)a) + i, i, *(int*)(((void*)a) + i)); | |
} | |
printf("Yeah, it has required us to typecast `void*` back to `int*` to read exactly typeof(int) bytes. But we did it, didn't we?"); | |
printf("Let's repeat it again, but now looking at integers in hex form, maybe we'll start to see something:\n"); | |
for (i = 0; i <= (L - 1) * sizeof(int); i++) { | |
printf("\t i == %d, address is %p, value is 0x%08x, which is %d\n", i, ((void*)a) + i, *(int*)(((void*)a) + i), *(int*)(((void*)a) + i)); | |
} | |
printf("Well, I guess it should make some sense now. Just look at the hexadecimal shift we have.\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment