Undefined behavior and temporaries
Asked Answered
T

2

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;
}
Toilsome answered 18/11, 2015 at 20:49 Comment(25)
Since the variables in question are not accessed after its scope, there's no problem. Yes, both example will output "good".Hypophysis
@wendelbsilva, knowing the history of the question, I can tell, you, OP will need something to substantitate your statement ;)Lasalle
My guess is that this is symmetrical with UB in pointers if you don't dereference them then there's no UB.Atul
The same examples with pointers instead of references would be well defined, won't they?Counterattack
I think, you might want another example, which would closer match the original question - something like int& ret = func()Lasalle
@Lasalle I added a third question. :)Toilsome
@Counterattack Clearly this is all perfectly fine with pointers. But references have restrictions that pointers don't have.Toilsome
@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 - #14731034Counterattack
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.Halfprice
@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.)Markham
@hvd Are you talking about the "invalid pointer value" rule? That's implementation-defined in C++.Halfprice
This code does NOT return a reference to a temporary. It returns reference to object i.Brewhouse
@Halfprice 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.Markham
@hvd Even more fun, as currently written "invalid pointer value" doesn't apply to pointers to automatic storage at all. P0137R0 is trying to fix that (among a bunch of other things).Halfprice
@Halfprice 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 behaviourBrewhouse
I think 3.8(6) [basic.life] has part of the answer.Killick
@Brewhouse Except it says neither "no diagnostic is required" nor "the behavior is undefined", so by [intro.compliance] it must be diagnosed.Halfprice
@Killick that refers to "before the storage which the object occupied is reused or released", however returning from the function releases the storage for iBrewhouse
@Halfprice "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.Markham
I agree with the case 3 being trick. But isnt storing an invalid pointer undefined behavior by omission?Hypophysis
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<int*>(nullptr)); //still valid codeMoia
@Moia 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.Markham
@hwd it is clearly stated in the question: "is this program guaranteed to output "good"? Yes, it is.Moia
@Moia 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.Markham
@Lasalle 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.Unbolted
U
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.

Unbolted answered 18/11, 2015 at 22:1 Comment(1)
@downvoter please explain what is the issue with my answerUnbolted
M
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.

Markham answered 18/11, 2015 at 21:25 Comment(0)
U
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.

Unbolted answered 18/11, 2015 at 22:1 Comment(1)
@downvoter please explain what is the issue with my answerUnbolted

© 2022 - 2024 — McMap. All rights reserved.