Skip to content

Instantly share code, notes, and snippets.

@gbezyuk
Created May 12, 2017 03:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gbezyuk/44362ee05bf0d5d20733af1e9b756f8e to your computer and use it in GitHub Desktop.
Save gbezyuk/44362ee05bf0d5d20733af1e9b756f8e to your computer and use it in GitHub Desktop.
C language addresses, arrays and pointers explained in details.
#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