-1

Sorry for silly title, couldn't think how it should be. I'm playing with unique_ptr, created pointer to unique_ptr (I know I shouldn't create pointer to a unique_ptr, I'm just playing to see what happened)

class A
{
  public:
    A() {std::cout<<"A const\n";}
    ~A() {std::cout<<"~A dest\n";}
    void fun()  {std::cout<<"A fun\n";}

};

int main()
{
   std::unique_ptr<A>* u1 = new std::unique_ptr<A>(new A);
   u1[1000]->fun();
   delete u1;
   return 0;
}

I expected output to be crash but it worked just as when I used u1[0]. And I completely have no idea why, I completely expect it to crash.

A const
A fun
~A dest

How come this code executes? It works with g++ and also in the online c++ shell http://cpp.sh/

SimpleAsk
  • 73
  • 5
  • Looks like UB, which explains the code working. – cigien May 12 '20 at 22:11
  • C++ doesn't have any kind of bounds-checking for pointers. `u1` is essentially a pointer to the first elements of an array of a single element, and if you go out of bounds of that array then it's your fault as the programmer. – Some programmer dude May 12 '20 at 22:13
  • And is there a reason you use use a pointer to a smart pointer to an object here? Pointers as containers should generally be avoided, if you need a container use `std::vector`. – Some programmer dude May 12 '20 at 22:16
  • @cigien: Someone who doesn't understand why this code seems to work will most likely also not understand what the letters 'UB' mean. – Robert Harvey May 12 '20 at 22:17
  • @Someprogrammerdude no reason, I'm just playing to see what will happen – SimpleAsk May 12 '20 at 22:18
  • @RobertHarvey I know what is undefined behavior – SimpleAsk May 12 '20 at 22:18
  • This program may appear to work today, but you have absolutely no guarantees that tomorrow it won't crash, and your computer will violently explode into tiny shards of metal, with screaming electrons escaping at the speed of life, trying to get away as far as they can from the upcoming apocalypse. So you shouldn't do it. – Sam Varshavchik May 12 '20 at 22:18
  • Good point. As the other comments say, you are accessing invalid memory, which invokes undefined behavior (UB). – cigien May 12 '20 at 22:19
  • 1
    You know what Undefined Behavior is, but you couldn't work out why your code succeeded? – Robert Harvey May 12 '20 at 22:19
  • @RobertHarvey int* t= new int(10); t[1] = 1000; never works. Here I also expect that will never works as I'm accessing wrong memory – SimpleAsk May 12 '20 at 22:22
  • @cigien int* t= new int(10); t[1] = 1000; never works. Here I also expect that will never works as I'm accessing wrong memory – SimpleAsk May 12 '20 at 22:23
  • That's the very definition of UB. To put it simply, "sometimes it works, sometimes it doesn't." If you expect any sort of consistent behavior, including a crash, then you don't really understand UB. By definition, UB is not consistent. – Robert Harvey May 12 '20 at 22:24
  • 1
    Calling fun() doesn't happen to access anything in any particular instance of A (when built with these compilers, on this platform). add a member to A, have fun() print it, watch it crash. Or declare fun() to be virtual, and watch it crash. – moonshadow May 12 '20 at 22:25
  • 1
    Or simply know that it is UB, and stop thinking about it. The minute you've identified UB, any logical analysis is pointless. – Robert Harvey May 12 '20 at 22:25
  • 1
    The behaviour of undefined behaviour is undefined. It is without limit, and inside the infinite possibilities are always looking like it works and never looking like it never works. You can't win with UB. – user4581301 May 12 '20 at 22:26
  • @RobertHarvey hm, I don't understand why in this case it's UB. I have a pointer to an object. And I'm accessing wrong memory, not the one where I have this pointer. How can I still get access to the object. – SimpleAsk May 12 '20 at 22:26
  • In C++, GIGO does not mean you'll get a crash. It may appear to work. That is the nature of garbage. As to _"Why it's possible..."_ is because C++ is not a nanny language. It gives you enough rope to shoot yourself fin the foot. You're responsible for obeying the rules. – Eljay May 12 '20 at 22:26
  • "Here I also expect" your expectation is wrong. C++ compilers not obligated to ensure that incorrect programs not working or crushing, they obligated to produce code so correct programs are working. Just think about difference btw that. – Slava May 12 '20 at 22:27
  • 2
    [You rent a hotel room. You put a book in the top drawer of the bedside table and go to sleep. You check out the next morning, but "forget" to give back your key. You steal the key! A week later, you return to the hotel, do not check in, sneak into your old room with your stolen key, and look in the drawer. Your book is still there. Astonishing!](https://stackoverflow.com/a/6445794/102937) – Robert Harvey May 12 '20 at 22:27
  • @SimpleAsk That's how UB works. Once you have it, all bets are off. The compiler would have been perfectly within it's right to display `hello World!` to the screen. – NathanOliver May 12 '20 at 22:28
  • @moonshadow thanks, that was really helpful comment! – SimpleAsk May 12 '20 at 22:33
  • @SimpleAsk: It is a helpful comment in the sense that, while his example may demonstrate the problem with your compiler on your platform, it might not on another compiler and platform. – Robert Harvey May 12 '20 at 22:35
  • @RobertHarvey but (at least I think so) with his comment I understand why it didn't crash(or at least didn't crash all/most of the time). Coz as fun() doesn't access any fields and isn't virtual it needs only the type and didn't really care about the actual object. – SimpleAsk May 12 '20 at 22:40
  • It's UB. *There is no "why,"* unless you wish to delve into compiler internals, an exercise that won't help you write better programs, though it might possibly help you write a better compiler. – Robert Harvey May 12 '20 at 22:45
  • @RobertHarvey when I added access to a files in fun() it started to crash, in my case all the time. And now I understand how it comes that in my original code it didn't crashed. I know that it doesn't mean that the one will always crash and the other will never crash. Before asking this question I was confused, now I'm not. – SimpleAsk May 12 '20 at 22:49
  • Sure. Do you think this new knowledge will help you write better C++? – Robert Harvey May 12 '20 at 23:06
  • @RobertHarvey yes, I think it's always better not just to know what, but to understand why it is so – SimpleAsk May 12 '20 at 23:20
  • OK, but do you really understand the underlying *why?* The example moonshadow gave forces a crash, but it might not on another compiler or platform. And the fix for the UB is the same in any case; stop doing out-of-bounds indexing. – Robert Harvey May 12 '20 at 23:25
  • @RobertHarvey UB is undefined, but in most cases it can be predicted. Because it's not some kind of magic, it's a machine and a program. I repeated already a lot of times that I clearly understand that I shouldn't use UB at all, I'm not talking here about how to fix a problem in the code, as I don't have such a problem, I'm now exploring how things work in C++ – SimpleAsk May 12 '20 at 23:34
  • I understand the motivation. But at the end of the day, all you'll have is the understanding of one behavior from one compiler in one particular set of circumstances. And what I am suggesting to you is that, unless you're prepared to dig into the compiler's internals, that information is not all that useful to you. If you're so inclined, you can examine the compiler output using a tool like GodBolt.org. What you're most likely to find out is that the UB code is writing to memory outside of the bounds you've defined for it. But you already knew that. – Robert Harvey May 12 '20 at 23:39
  • @RobertHarvey I don't think I will have many question like this one. When the program crashes, even when it's UB, there is a very exact reason why it crashed. It's like with my other example: if I write int* t= new int(10); t[1] = 1000; I know that t[0] won't become 1000. And I also understand why this code in most cases won't crush. – SimpleAsk May 13 '20 at 00:06
  • "but in most cases it can be predicted" - careful; this is not actually true. The compiler is permitted to assume that UB won't happen when performing optimizations. This means that it could do things like optimize your call to fun() away entirely if it can determine that it always involves UB, and so not printing anything at all would be a valid compilation of your program; and compilers really do do this in similar situations. In this case you get away with it but people who think they can predict UB get caught by this ALL THE TIME – moonshadow May 13 '20 at 07:58

2 Answers2

3

Technically, u1[1000]->fun(); exhibits undefined behaviour, and as such, "anything can happen".

In practical terms, you execute A::fun() with an invalid this pointer, and since A::fun() references no member variables or virtual functions, you get away with it. Which is probably bad news, rather than good, since you have a bug there just waiting to happen.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
1

How come this code executes?

Because the behaviour of the program is undefined.

There is no guarantee that the program will crash, nor is there a guarantee that it won't crash. There are no guarantees about the behaviour whatsoever.

I expected output to be crash

You cannot rely on the program to behave the way you expect when the behaviour is undefined.


int* t= new int(10); t[1] = 1000; never works. Here I also expect

How do you know that it "never works"? Have you observed every execution of every program where that has been written, compiled with every version of every compiler both past and future, with and without all of the various options that the compilers have to offer, on every system in existence? I suspect that you haven't. Even if we were to assume that you have, there still would be no guarantee that it behave in the way you expect.

This program "never works" in the same sense that the program in the question "never works". The behaviour is undefined and anything might happen. It might:

  • Always crash
  • Never crash
  • Sometimes crash
  • Crash when you run it, but not when someone else does
  • Always have specific output
  • Never have that output
  • Sometimes have that output
  • Always have no output
  • Never have any output
  • ...

This list is infinite.

I think even UB in most cases have some explanation, like in my case why it's not crashing

There is no answer to that within C++. You must instead read the assembly language program generated by the compiler and ask "why does this assembly program crash", then answer may be found in the manuals for your CPU and operating system.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • mm, I meant that after line t[1] = 1000 t[0] will never be 1000. That's the meaning of "never works" I meant. In this I already understand that the code works and didn't crash coz fun() is pretty the same as a static function here so it needs only the type, not the actual object as it doesn't access any fields and isn't virtual. – SimpleAsk May 12 '20 at 22:37
  • @SimpleAsk There is no guarantee that it would crash even if you did access fields, or the function was virtual. Nor would there be a guarantee that the shown program which doesn't access fields doesn't crash. The behaviour is undefined. Nothing is guaranteed. Anything can happen as far as the language is concerned. – eerorika May 12 '20 at 22:39
  • @SimpleAsk in duplicate I pointed it is clearly described why you do not observe crush in your code. Note that fact (neither the fact seeing expected output) does not make your program correct or working. That's unfortunate price of flexibility and power of C++. – Slava May 12 '20 at 22:41
  • @Slava yes, I already have read that, thanks! Of course I understand that I should rely on anything like that, just I think even UB in most cases have some explanation, like in my case why it's not crashing(at least not crushing most of the times), now I got my understanding why. – SimpleAsk May 12 '20 at 22:45
  • @SimpleAsk "just I think even UB in most cases have some explanation" I am afraid you think wrong. UB has only one explanation - language standard says so. Looks like you are mixing UB in your code and observed behaviour. – Slava May 12 '20 at 22:50
  • @Slava but now I have clear explanation why it happened that my original fun() didn't crash (in my case) at all, but when I added usage of a field inside fun() it started to crush (in my case) all the time. – SimpleAsk May 12 '20 at 22:53