14

1) Is it undefined behavior to return a reference to a temporary, even if that reference is not used? For example, is this program guaranteed to output "good":

int& func()
{
    int i = 5;
    return i;
}

int main()
{
    func();

    cout << "good" << endl;
    return 0;
}

2) Is it undefined behavior to simply have a reference to an object that no longer exists, even if that reference is not used? For example, is this program guaranteed to output "good":

int main()
{
    int *j = new int();
    int &k = *j;
    delete j;

    cout << "good" << endl;
    return 0;
}

3) Is it undefined behavior to combine these?

int& func()
{
    int i = 5;
    return i;
}

int main()
{
    int& p = func();

    cout << "good" << endl;
    return 0;
}
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Since the variables in question are not accessed after its scope, there's no problem. Yes, both example will output "good". – wendelbsilva Nov 18 '15 at 20:51
  • 3
    @wendelbsilva, knowing the history of the question, I can tell, you, OP will need something to substantitate your statement ;) – SergeyA Nov 18 '15 at 20:53
  • My guess is that this is symmetrical with UB in pointers if you don't dereference them then there's no UB. – 101010 Nov 18 '15 at 20:54
  • The same examples with pointers instead of references would be well defined, won't they? – Leeor Nov 18 '15 at 20:54
  • I think, you might want another example, which would closer match the original question - something like `int& ret = func()` – SergeyA Nov 18 '15 at 20:54
  • @SergeyA I added a third question. :) – David Schwartz Nov 18 '15 at 20:55
  • @Leeor Clearly this is all perfectly fine with pointers. But references have restrictions that pointers don't have. – David Schwartz Nov 18 '15 at 20:56
  • @DavidSchwartz, yes, I was trying to refer to the same duality as 101010, but I agree it's not convincing. By the way, found this - http://stackoverflow.com/questions/14730534/dangling-references-and-undefined-behavior – Leeor Nov 18 '15 at 20:59
  • 1
    1 and 2 should be OK - I know of no rule that they break, though it's hard to prove a negative. 3 is trickier owning to [dcl.ref]/5 ("A reference shall be initialized to refer to a valid object or function."), but that language is clearly defective as written (it would require a diagnostic, which is obviously impossible), so what should happen is unclear. – T.C. Nov 18 '15 at 21:00
  • 1
    @DavidSchwartz No, this is not all perfectly fine with pointers. The pointer equivalent of your third question has UB. (At least definitely so in C, and I highly suspect C++ did not change this.) –  Nov 18 '15 at 21:01
  • @hvd Are you talking about the "invalid pointer value" rule? That's implementation-defined in C++. – T.C. Nov 18 '15 at 21:03
  • 2
    This code does NOT return a reference to a temporary. It returns reference to object `i`. – M.M Nov 18 '15 at 21:05
  • @T.C. Yes. And right, with a footnote explaining that the implementation-defined behaviour may result in a crash ("a system-generated runtime fault"). Wow, C++ did change that to make it not UB, though it's still not fine. –  Nov 18 '15 at 21:05
  • @hvd Even more fun, as currently written "invalid pointer value" doesn't apply to pointers to automatic storage at all. [P0137R0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0137r0.html) is trying to fix that (among a bunch of other things). – T.C. Nov 18 '15 at 21:07
  • @T.C. I think it's clear that "A reference shall be initialized to refer to a valid object or function" is not supposed to be diagnosable, e.g. the *Note* following which gives an example and states that it is undefined behaviour – M.M Nov 18 '15 at 21:08
  • I think 3.8(6) [basic.life] has part of the answer. – NathanOliver Nov 18 '15 at 21:09
  • @M.M Except it says neither "no diagnostic is required" nor "the behavior is undefined", so by [intro.compliance] it must be diagnosed. – T.C. Nov 18 '15 at 21:10
  • @NathanOliver that refers to "before the storage which the object occupied is reused or released", however returning from the function releases the storage for `i` – M.M Nov 18 '15 at 21:11
  • @T.C. "Invalid pointer value" doesn't have a formal definition, it's not in italics in [basic.std.dynamic.deallocation]. There's definitely missing wording, but I'm not sure it's right to conclude that for now, technically it only refers to dynamically allocated memory. –  Nov 18 '15 at 21:13
  • I agree with the case 3 being trick. But isnt storing an invalid pointer undefined behavior by omission? – wendelbsilva Nov 18 '15 at 21:20
  • Come on guys, reference can be seen as just a const pointer. What's the problem in having an invalid pointer around? Just don't try using it. int& i=*(static_cast(nullptr)); //still valid code – Lao Nov 18 '15 at 21:26
  • 1
    @Lao That depends on whether you mean "valid code" as "code that doesn't crash on my system" or as "code that follows the rules defined by the C++ specification". The former is what you can answer by just trying it, as you pretty much suggest, but this question is about the latter. –  Nov 18 '15 at 21:30
  • @hwd it is clearly stated in the question: "is this program guaranteed to output "good"? Yes, it is. – Lao Nov 18 '15 at 21:39
  • 1
    @Lao Yes, it is indeed clearly stated. The question doesn't ask 'will this program output "good"?', it asks 'is this program guaranteed to output "good"?'. As in, guaranteed by the rules by which C++ is defined. –  Nov 18 '15 at 21:41
  • @SergeyA if there is a compiler exploiting case 1 and 2 as UB then that is important information to add to the question. Especially because it would be surprising and documenting it would be helpful for projects like the [UB Canaries](http://blog.regehr.org/archives/1234). – Shafik Yaghmour Nov 19 '15 at 10:25

2 Answers2

6

2) Is it undefined behavior to simply have a reference to an object that no longer exists, even if that reference is not used?

No. The rule that references must refer to a valid object applies when the reference is inintialised. The rule has already been quoted in the comments: "A reference shall be initialized to refer to a valid object or function." This rule has not been violated in your program, and there are no other restrictions on references requiring them to refer to valid objects or functions after initialization.

The standard has a few examples involving dangling references, such as [class.temporary]5.4:

struct S { int mi; const std::pair<int,int>& mp; };
S a { 1, {2,3} };
S* p = new S{ 1, {2,3} };  // Creates dangling reference.

and doesn't say for any such example that the mere existence of a dangling reference is invalid. Although it's never explicitly stated as allowed, the absence of any rule prohibiting it is enough to allow it.

1) Is it undefined behavior to return a reference to a temporary, even if that reference is not used?

No. The construction of the result (the initialisation of the reference) happens in the context of the called function. There can even be extra code in the called function that runs after the construction of the result: destructors for local objects run after the construction of the result has finished. Since the reference is initialized to a valid object, this is just like your second question, that same rule still hasn't been violated.

3) Is it undefined behavior to combine these?

Yes. In your example, p is not initialised to refer to a valid object or function. As you can tell from the comments on your question, there are issues with the wording in the standard, but the intent of this rule pretty clearly is that if it is violated, the behaviour is undefined.

5

I don't see any rules that forbid case 1 and 2 nor can I find a relevant defect report either.

All we really have from the draft C++ standard is from section 8.3.2 [dcl.ref]:

[...]A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior.[...]

which does not apply to case 1 since we are not initialing a reference and neither case 2 since the object is valid when we initialize the reference.

This does seem to apply to case 3. So what does valid object mean is subject of the following defect report. The defect report which covers this topic is still open and therefore we can only get a a feel for current thinking which is that this should be undefined behavior.

If we look at defect report 453: References may only bind to “valid” objects , which deals with what it means to bind a reference to an invalid object. The current proposed resolution says:

[...]If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (8.5.3 [dcl.init.ref]), nor a region of storage of suitable size and alignment to contain an object of the reference's type (1.8 [intro.object], 3.8 [basic.life], 3.9 [basic.types]), the behavior is undefined. [...]

So we can say the current thinking is that this should be undefined behavior but currently this is defect and so we can't say for sure until this defect report is resolved. I would err on the side of caution and assume it is undefined behavior.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740