Throwing an array local to a try block
Asked Answered
H

3

14

From C++ Primer 18.1.1:

If the [thrown] expression has an array or function type, the expression is converted to its corresponding pointer type.

How is it that this program can produce correct output of 9876543210 (g++ 5.2.0)?

#include <iostream>
using namespace std;

int main(){

    try{
        int a[10] = {9,8,7,6,5,4,3,2,1,0};
        throw a;
    }
    catch(int* b) { for(int i = 0; i < 10; ++i) cout << *(b+i); }

}

From the quote, throw a would create an exception object of type int* which is a pointer to the first element of the array. But surely the array elements of a would be destroyed when we exit the try block and enter the catch clause since we change block scope? Am I getting a false positive or are the array elements "left alone" (not deleted) during the duration of the catch clause?

Henkel answered 13/1, 2016 at 13:26 Comment(2)
The numbers happen to still be there, until someone else reuses the stack space. One possible effect of undefined behavior is "seems to work".Iguana
What output were you expecting that would indicate that the elements had been destroyed?Liqueur
E
11

Dereferencing a dangling pointer is Undefined Behaviour.

When a program encounters Undefined Behaviour, it is free to do anything. Which includes crashing and making daemons fly out of your nose, but it also includes doing whatever you expected.

In this case, there are no intervening operations that would overwrite that part of memory and access past the stack pointer (below it, because stack grows down on most platforms) is normally not checked.

So yes, this is a false positive. The memory is not guaranteed to contain the values any more and is not guaranteed to be accessible at all, but it still happens to contain them and be accessible.

Also note, that gcc optimizations are rather infamous for relying on the program not invoking undefined behaviour. Quite often if you have an undefined behaviour, the unoptimized version appears to work, but once you turn on optimizations, it starts doing something completely unexpected.

Esp answered 13/1, 2016 at 13:35 Comment(0)
S
8

But surely the array elements of a would be destroyed when we exit the try block and enter the catch clause since we change block scope?

Correct.

Am I getting a false positive or are the array elements "left alone" (not deleted) during the duration of the catch clause?

They're not "left alone". The array is destroyed as you correctly assumed. The program is accessing invalid memory. The behaviour is undefined.

How is it that this program can produce correct output of 9876543210 (g++ 5.2.0)?

There is no correct output when the behaviour is undefined. The program can produce what it did because it can produce any output when the behaviour is undefined.

Reasoning about UB is usually pointless, but in this case, the contents of the invalid memory could be identical if the part of the memory had not yet been overwritten by the program.

Sentimental answered 13/1, 2016 at 13:35 Comment(5)
Please pardon me if i am wrong, The data is released only after control comes out of the function right?Zoltai
@sameerasy, no the data is released when it goes out of scope, so at end of the block. But releasing plain old data does not overwrite them with anything, just marks the memory region as available for reuse. And there is nothing to reuse it in your example.Esp
While this is true from the perspective of the C++ standard, the question could be quite reasonably interpreted as a question about the runtime behaviour of the compiled binary, in which case the C++ standard is irrelevant and the correct answer would be that the destructor of a is implemented as a no-op, so the memory contents are unchanged.Sordid
@Benno. the problem is that the compiler is free to do whatever it pleases in this case and gcc is well known for taking pleasure in exercising that option. In this case it could conceivably optimize away the array (as it has no valid use) and then it could throw NULL or throw a point to where the array would be, but never initialize it or throw uninitialized value as pointer or not call the handler and possibly several other things. Don't ever assume the compiler will compile code with undefined behaviour the obvious way. It won't (gcc in particular; msc++ often does)!Esp
@Jan Yes I know, my point is that it's of course invalid to ask "Given this cpp file which contains undefined behaviour, what will happen if I compile and run it?", but the question "Given this binary which was produced from this cpp code, why do I observe the following behaviour" is still valid and not related to the C++ standard any more.Sordid
P
5

You will find later in this chapter a Warning:

Throwing a pointer requires that the object to which the pointer points exist wherever the corresponding handler resides.

so your example would be valid if a array were static or global, otherwise its UB. Or (as Jan Hudec writes in comment) in the enclosing block of the try/catch statement.

Postaxial answered 13/1, 2016 at 13:41 Comment(1)
Almost. Actually, since the throw and catch are in the same function, it would also be valid if a was in the enclosing block of the try/catch statement.Esp

© 2022 - 2024 — McMap. All rights reserved.