0

I've been trying to understand pointers, in particular the pointer[i] notation which is the equivalent of *(pointer + i), ie dereferencing the value at the next address, so if its an int pointer than the next 4 byte address. And I tried running the following code below:

#include <stdio.h>

int main(void){
  int x = 10;
  int y = 20;

  int* z = &y;
  printf("address of x: %u ", &x);
  printf("address of y: %u", &y); 
  printf("value at address: %u \n", *(z+1));

}

I noticed that 'y' was stored 4 bytes before 'x', and thought that i would be able to return the value stored at x's address by z[1] or *(z+1), which worked fine.

However when I removed the first 2 printf's, I ended up with the address being outputted instead of the actual value (10), here is the code for this case:

#include <stdio.h>

int main(void){
  int x = 10;
  int y = 20;

  int* z = &y;
  printf("value at address: %u \n", *(z+1));

}

I am assuming that *(z+1) is no longer referring to the same area of memory as the variable 'x' , but I am confused as to why this change in the code caused this.

Initially i thought it was one off, so I tried this multiple times and got the same results each time.

Whats even more confusing is how the code works as expected in both cases on ubuntu and macos, but not on windows.

Any help regarding this would be great, thanks.

IAhmed12
  • 7
  • 3
  • Have you tried perhaps performing a stack trace for both cases with a debugger? – mediocrevegetable1 Apr 05 '21 at 10:47
  • are you sure the value that is printed in the second example is not just some garbage value? – Raildex Apr 05 '21 at 10:50
  • It could be, but I still have no idea why removing those 2 prints would cause that. – IAhmed12 Apr 05 '21 at 10:52
  • Why would the compiler waste memorey and keep `x` which is never used anywhere in your program? It is very likely that that variable is not existing at all in your executable. You also invoke undefined behaviour by using unvalid format specifiers in `printf` and accessing memory out of bounds. – Gerhardh Apr 05 '21 at 10:53
  • Right, that makes sense, but is the action of removing unused variables dependent on the compiler for that OS, as in linux and macos, the variable x still exists in the executable?? – IAhmed12 Apr 05 '21 at 10:57
  • It may depend not only the compiler but also the optimization level. – MikeCAT Apr 05 '21 at 11:05
  • Okay, this has really cleared up my understanding on this. I added a pointer to x, and that seemed to cause x to exist in the executable, and now it works fine. Thanks for help. – IAhmed12 Apr 05 '21 at 11:14
  • 2
    to print pointers you must use to use `%p` instead of `%u` – phuclv Apr 05 '21 at 11:40
  • wrong! you can still print the pointer's value as an unsigned decimal using %u. %p just formats the pointer value into 0x[hex]. Coming to the original question: windows OS kernel is completely different a kernel of unix-compliant OS (ubuntu/mac), meaning when asked to print unassigned address from the stack such as *(y+1) there will be some deep kernel routine for dealing with it(throw exception or trap and emulate for example) different than in a unix-based OS. bottom line, this unusual behavior probably only happens when asking for unassigned address from the stack memory section. – LazerDance Apr 05 '21 at 12:27
  • Thanks for you answer, just for further clarification, by unassigned address, are you referring to the fact that the area in memory which 'x' refers to (that represents the value 10) is no longer present in the executable, and therefore is an "unassigned address" ? – IAhmed12 Apr 05 '21 at 13:10
  • @LazerDance you can do many things that are still wrong. Passing a pointer for any other format specifier than '%p' causes undefined behavior. It not only may (or may not) change formatting. It may also consume a different number of bytes from parameter list. You might have heard about 64 bit systems. Many still use 32 bit integers and only pointers are 64 bits long. – Gerhardh Apr 05 '21 at 18:52
  • @IAhmed12 in your example the pointer *(z+1) points to unassigned address, whereas *(z-1) presumably points to x. give *(z-1) a try and see. for further explanation read about the stack and how local variables are stored. – LazerDance Apr 05 '21 at 20:34
  • @Gerhardh I totally agree with you that %p is the safe way to print pointers. However, printing using %u(32 bit) or %lu (64 bit) isn't wrong per se. it's just a different format and returns the same result. %p is the best practice – LazerDance Apr 05 '21 at 20:34

1 Answers1

0

Here I can only answer part of your question and present my observations.

Here is the code modified.

#include <stdio.h>

int main(void) {
  int x = 10;
  int y = 20;
  int *intp = &y;

  //  printf("address of x is %p.\n", &x);
  //  printf("address of y is %p.\n", &y);
  printf("*(intp + 1) is %u.\n", *(intp + 1));
  printf("*(intp)     is %u.\n", *(intp));
  printf("*(intp - 1) is %u.\n", *(intp - 1));
  printf("difference of addresses: &x - &y = %d.\n", &x - &y);

  return 0;
}

I get consistent results for *(intp), *(intp - 1) and &x - &y, while *(intp + 1) would change. One of the output is

*(intp + 1) is 2028820428.
*(intp)     is 20.
*(intp - 1) is 10.
difference of addresses: &x - &y = -1.

However, if I comment out the line using &x,

//  printf("difference of addresses: &x - &y = %d.\n", &x - &y);

the output would be consistent, as follows

*(intp + 1) is 10.
*(intp)     is 20.
*(intp - 1) is 0.

My idea is that, on my gcc 10.2.0 on Linux,

  • in the first case, when &x is used, x is stored at a lower address than y, by 4 bytes. As int is of 4 bytes on my computer, &x - &y always is -1.
  • in the second case, when &x is not used, x is stored at a higher address than y, by 4 bytes.

So the compiler did set space to store x and y, even when x is not used. But I don't know why their positions would differ in the two cases. Compilers could try to optimize the code, but I have little knowledge of that.

Besides, if you want to try to use gdb to test the addresses, you could try

gcc -g thecode.c
gdb a.out
(gdb) break main
(gdb) run
(gdb) p &x
(gdb) p &y
(gdb) p &x + 1
(gdb) step
(gdb) (press enter directly to use the last same command...)
(gdb) quit
liginity
  • 311
  • 3
  • 7
  • `*intp` is an `int`, printing it with `%u` invokes UB. And `&x - &y` is a `ptrdiff_t` which you also invoke UB by printing it with `%d` instead of [`%td`](https://stackoverflow.com/q/7954439/995714) – phuclv Apr 06 '21 at 02:52