-1

main.cpp

#include <iostream>

int main(){
    int x = 1, j = 2;
    int *p = &j;
//    std::cout << &x << std::endl;
    std::cout << *(p-1) << std::endl;
}

I get different output with the line std::cout << &x << std::endl; commented out and uncommented. Are integers stored in consecutive locations in stack?

values:

commented: 0 (probably garbage value)
uncommented: 1
Vivek
  • 448
  • 2
  • 11
  • 17
    Your code exhibits undefined behavior and therefore you should not bother reasoning about the output of the program. – Cory Kramer Oct 03 '22 at 13:33
  • @CoryKramer, could you explain what is the undefined behaviour? – Vivek Oct 03 '22 at 13:35
  • 1
    To answer the title specifically, there are a lot of things that influence way of storing variables on stack. I suggest reading about calling conventions to have better understanding about the concept. [Here](https://www.agner.org/optimize/calling_conventions.pdf) is a good article. – Karen Baghdasaryan Oct 03 '22 at 13:35
  • 2
    `p-1` is undefined, you do arithmetic out-of-bounds of the object pointed to. – Caleth Oct 03 '22 at 13:35
  • 4
    If you don't require an address by using `&`, these will either be stored in registers or not at all. (You need to adjust your mental model of how things work.) – molbdnilo Oct 03 '22 at 13:36
  • 1
    @Vivek Pointer arithmetic is only well-defined within the same array (where non-arrays are considered arrays of one value). Since `p-1` is not within the same array as `p`, the subtraction is undefined. – Miles Budnek Oct 03 '22 at 13:37
  • While it's certainly UB, it's a completely valid question _if_ OP specifies ABI to reason about. E.g., on ItaniumABI, which is used by most, if not all x86_64 compilers nowadays, the result is specified I think. – lorro Oct 03 '22 at 13:41
  • 1
    The answer few want to give is that your compiler probably optimises `x` out of existence if it's not used which is why you're getting different results. They're all right that what you're doing isn't undefined and please (please!) don't write code that pays games with how you think the stack is structured. But 'behind the scenes' that is probably what is going when you 'peek' behind he curtain like this. Many programmers have been so disciplined about 'UB' that they refuse to discuss anything identified as UB. – Persixty Oct 03 '22 at 13:43
  • 7
    @Iorro, the result is still undefined behaviour, since the compiler may optimize things, as seen by the compiler seemingly getting rid of `x` when it's unused. – CoffeeTableEspresso Oct 03 '22 at 13:43
  • 1
    https://en.cppreference.com/w/cpp/language/ub – Jesper Juhl Oct 03 '22 at 13:53
  • 3
    *how are integers stored in stack?* That's an implementation detail, and not specified by the C++ standard. C++ standard describes how C++ program behave in an abstract C++ machine model. The abstract machine doesn't specify a **stack** at all, it specifies *storage*. The compiler translates the C++ code to the abstract C++ machine model, and the converts that to executable code for your platform. Also, C++ is not a nanny language, so if you break the rules (like you did here), the compiler trusts that your program is valid and well formed. Alas, it's not. – Eljay Oct 03 '22 at 14:21
  • @Vivek The odd thing is that if someone were to just look at the program you wrote, the program looks just plain strange. Why would you want to get the contents of an item at an address before one of your variables, without knowing what in the world is lying there? In other words, the simplest reasoning is the correct reasoning. Talking about "stack" is over-engineering the reason why your program has weird behavior. – PaulMcKenzie Oct 03 '22 at 16:05
  • FYI, the compiler can store variables in *registers* and not use the stack. – Thomas Matthews Oct 03 '22 at 17:27

2 Answers2

7

You didn't use x, so the compiler didn't store it anywhere.

Remember, when you write C++ you aren't writing a program. You're describing the behavior of the program you want your compiler to write for you. The compiler can write any program as long as its behavior matches the behavior you described. If you declare a variable whose initialization has no observable side-effects and you never use it anywhere then you haven't described any behavior. The compiler doesn't need to generate any code to deal with that, so it will often omit variables like that entirely.


Note that in this case the instructions you gave don't have any defined behavior, so the compiler can write any program it wants. Attempting to offset a pointer outside the array it points to is undefined (where non-array variables are treated like arrays of one element), so p - 1 is not defined.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
1

What you're doing is Undefined Behaviour and you should not try and use such details in any serious program but try this:

#include <iostream>

int main(){
    int a = 42;
    int b = 1;
    int c = 2;
    int d = 2;
    int e = 2;
    int *p = &c;
    int *q=p+1;
    
    std::cout << "sizeof(int) = " << sizeof(int) << '\n';
    std::cout << "&a = " << &a << '\n';
    std::cout << "&b = " << &b << '\n';
    std::cout << "&c = " << &c << '\n';
    std::cout << "&d = " << &d << '\n';
    std::cout << "&e = " << &e << '\n';
    std::cout << "q  = " << q << '\n';
}

Typical Output:

sizeof(int) = 4
&a = 0x7fff7c418d74
&b = 0x7fff7c418d78
&c = 0x7fff7c418d7c
&d = 0x7fff7c418d80
&e = 0x7fff7c418d84
q  = 0x7fff7c418d80

You will probably see q = &d (as here) or possibly q = &b;

Then comment out the output line for the one that matches. For me that's std::cout << "&d = " << &d << '\n';

Typical Output:

sizeof(int) = 4
&a = 0x7fff62baf498
&b = 0x7fff62baf49c
&c = 0x7fff62baf4a0
&e = 0x7fff62baf4a4
q  = 0x7fff62baf4a4

The actual addresses will vary between execution but typically bear fixed relationships in terms of the size of an int (typically 4).

On my platform you'll see an int is 4 bytes and the pointers are output as hexadecimal addresses and all differ by 4. That is the most common case.

That is, in this example and likely on your platform you'll see the local variables occupy a consecutive block of memory given away by the pointer addresses.

What you're most likely seeing is that when you don't use x in your code (d in mine) the compiler optimises it out of existence.

Pointer arithmetic (e.g. p-1) is only valid within an array. However p+1 is always legal because variables always like singleton arrays and there's a special rule for being allowed to point to one past the end of an array.

What is undefined is to dereference p+1 even if it points to one of the other variables (which it likely will). It is not a 'safely derived pointer'.

p-1 will probably give you the result you expect! But the result of p-1 is Undefined.

This is illustrative to understand how the stack works. Never ever (ever!) do this as part of any serious coding effort.

Persixty
  • 8,165
  • 2
  • 13
  • 35