1

I'm testing pointers with C++ and I just noticed that when I do something like this:

int main(){
int* test;
std::cout << test << std::endl;

return 0;
}

it would output 0 and when I do something like this:

int main(){
int* test;
&test;
std::cout << test << std::endl;

return 0;
}

it outputs a valid memory address. Does anyone have an idea on why this happens?

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 4
    Its uninitialized. It could print anything. – tkausl Dec 23 '19 at 14:40
  • I guessed so, but the assembly code also changes when I get it's reference –  Dec 23 '19 at 14:41
  • 1
    Like tkausl said, it is uninitialised, but a compiler can make it zero if it so wishes to. Your `&test;` line doesn't do anything and should not show up in the final assembly. Which compiler are you using? – NotAProgrammer Dec 23 '19 at 14:43
  • 2
    You're code has undefined behavior. Any result is correct. – NathanOliver Dec 23 '19 at 14:44
  • 1
    When you print the address of something, it may happen that something actually gets an address due to this. In your first example, `test` does not necessarily need to exist in memory - a register would be sufficient as well. In the 2nd case, you explicitly ask for the address - so, it has to get one. – Scheff's Cat Dec 23 '19 at 14:44
  • FYI: [SO: Why does a const int not get optimized by the compiler (through the symbol table) if another pointer points to its reference?](https://stackoverflow.com/a/53349946/7478597) – Scheff's Cat Dec 23 '19 at 14:46
  • Yeah in the first assembly the pointer doesn't get pushed to the stack, I guess it was using a register as it's value, in the second it pushes some value onto it, I'm using g++ btw –  Dec 23 '19 at 14:46
  • @Cufox You are a magician! – Vlad from Moscow Dec 23 '19 at 14:54
  • I guess it getting a value makes more sense, what's confusing me now is why that value is a valid memory address and not a garbage value –  Dec 23 '19 at 15:10
  • Define "valid". How do you distinguish between "a valid memory address" and "a garbage value", when neither actually points to an `int` that exists? – Lightness Races in Orbit Dec 23 '19 at 15:13
  • as in it looks normal and I can work with it, do pointers get special garbage values? –  Dec 23 '19 at 15:14
  • Define "looks normal". In what sense can you "work with it"? – Lightness Races in Orbit Dec 23 '19 at 15:15
  • I can dereference it, perform memmove on it, it works like a normal pointer, except I have no idea where it points and so far no seg faults were thrown –  Dec 23 '19 at 15:20
  • That you "can" do those things is an illusion. Do not "expect" segmentation faults from the use of invalid pointers. That is only one possible result. The operating system doesn't detect _all_ bad accesses (unless you use some debug tool to make it do so), usually only those that cross page boundaries or such. – Lightness Races in Orbit Dec 23 '19 at 15:22
  • I see! thank you this makes much more sense now :> –  Dec 23 '19 at 15:23
  • No probs. I've added that to my answer as it seems to be a critical part of your question :) – Lightness Races in Orbit Dec 23 '19 at 15:24

1 Answers1

2

tl;dr: Compilers are complex, and undefined behaviour allows them to do all sorts of things.


int* test;
std::cout << test << std::endl;

Using test (even just to evaluate its own value!) in this manner when it hasn't been given a value is not permitted, so your program has undefined behaviour.

Your compiler apparently uses this fact to take some particular path. Perhaps it's assuming a zero value, or it's prepared to optimise away the variable and leave you only with some hardcoded thing. It's arbitrarily picked zero for that thing, because why not? The value is unspecified by the standard, so that's fine.

&test;

This is another thing. It is perfectly legal to take the address of an uninitialised thing, so this aspect of your program is well-defined. It appears that this triggers a path in the compiler that prepares to create actual, honest-to-god storage for the pointer. This odr-use effectively prevents any of the optimise-it-out machinery. Somehow, that's taken it down a road that doesn't trigger the "pretend it's zero" case, and you end up with (possibly) some actual, memory read instead; that memory read results in the unspecified value that you have come to expect from outputting uninitialised things.

That value is still "garbage", though. You indicate that you "can" deference it, that you "can" memmove it, that you "can" work with it without triggering a segmentation fault. But this is all an illusion! Do not "expect" segmentation faults from the use of invalid pointers. That is only one possible result. The operating system doesn't detect all bad accesses (unless you use some debug tool to make it do so), usually only those that cross page boundaries or such.

Anyway, the specifics of the above are complete speculation but it shows the sort of factors that can go into different outcomes of programs with undefined behaviour. Ultimately there is not a lot of point in trying to rationalise about this sort of code, and there is certainly no point in writing it!

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Yup I think this explains it fully, guess I should stop questioning undefined behaviour! –  Dec 23 '19 at 15:24
  • @Cufox You'll only make yourself mad ;) That being said, there are benefits to having some familiarity with common outcomes; if you're hunting some bug, that experience will make spotting UB a little easier. The number of times I've muttered "this smells like UB..." and refocused my attention on buffer overruns etc and thus found the fault, is not insubstantial. – Lightness Races in Orbit Dec 23 '19 at 15:25
  • yeah it's kinda why I was testing this, I thought I saw some source code of someone actually using a pointer like that and I wanted to test it out! thx again! :) –  Dec 23 '19 at 15:29
  • 1
    @Cufox Cool. Don't forget to file a bug report to that person ;) – Lightness Races in Orbit Dec 23 '19 at 15:31