0

It is, as far as I have known, been a good rule that a pointer like argument type to a function should be a pointer if the argument can sensible be null and it should be a reference if the argument should never be null.

Based on that "rule", I have naiively expected that doing something like someMethodTakingAnIntReference(*aNullPointer) would fail when trying to make the call, but to my surprise the following code is running just fine which kinda makes "the rule" less usable. A developer can still read meaning from the argument type being reference, but the compiler doesn't help and the location of the runtime error does not either.

Am I misunderstanding the point of this rule, or is this undefined behavior, or...?

int test(int& something1, int& something2)
{
    return something2;
}

int main()
{
    int* i1 = nullptr;
    int* i2 = new int{ 7 };

    //this compiles and runs fine returning 7.
    //I expected the *i1 to cause an error here where test is called
    return test(*i1, *i2); 
}

While the above works, obviously the following does not, but the same would be true if the references were just pointers; meaning that the rule and the compiler is not really helping.

int test(int& something1, int& something2)
{
    return something1+something2;
}

int main()
{
    int* i1 = nullptr;
    int* i2 = new int{ 7 };


    //this compiles and runs returning 7.
    //I expected the *i1 to cause an error here where test is called
    return test(*i1, *i2); 
}
JoeTaicoon
  • 1,383
  • 1
  • 12
  • 28
  • 1
    Both snippets invoke UB. – Jarod42 Jan 04 '18 at 23:43
  • 1
    The function signature tells the *caller* that `nullptr` is not an acceptable value but it can't stop the caller from screwing up. – Galik Jan 04 '18 at 23:44
  • Dereferencing a nullpointer is UB, except in a `typeid` expression. However, as far as I know there is no normative language in the standard that says so. Infamously the C++03 standard referred in a note to language elsewhere that would make dereferencing a nullpointer UB, but that location elsewhere was not disclosed, and nobody's found it. – Cheers and hth. - Alf Jan 04 '18 at 23:45
  • 2
    The rule is good, as the error is not in `test` function, but on caller side (who should check pointer before dereferencing it.) – Jarod42 Jan 04 '18 at 23:45
  • A similar function taking *pointers* would suggest that passing `nullptr` was okay. The references says it's not. The references demand a valid object and the caller knows he has to provide one. – Galik Jan 04 '18 at 23:47
  • Reading from `something1` in the function is certainly UB, even if the status of `*i1` is unclear – M.M Jan 04 '18 at 23:49
  • I now realize that my (somewhat embarrassing) error of view was mainly that I considered the call test(*i1,...) as reading the value at *i1 and passing it to the function as a regular int on the stack, at _call site_ while in reality it is of course just the pointer being sent to the function, irregardless that the _notation_ implies that the de-referencing happens before pushing arguments on stack. (assuming stack arguments is even how args are passed in the first place, which is also not specified in c++) – JoeTaicoon Jan 05 '18 at 06:00

2 Answers2

1

Writing test(*i1, *i2) causes undefined behaviour; specifically the part *i1. This is covered in the C++ Standard by [expr.unary.op]/1:

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

This defines the behaviour of *X only for the case where X points to an object or function. Since i1 does not point to an object or function, the standard does not define the behaviour of *i1, therefore it is undefined behaviour. (This is sometimes known as "undefined by omission", and this same practice handles many other uses of lvalues that don't designate objects).


As described in the linked page, undefined behaviour does not necessitate any sort of diagnostic message. The runtime behaviour could literally be anything. The compiler could, but is not required to, generate a compilation warning or error. In general, it's up to the programmer to comply with the rules of the language. The compiler helps out to some extent but it cannot cover all cases.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • **−1** The attempted reasoning is wrong. E.g. there is no object beyond the end of an array. Yet the act of dereferencing a pointer to it, e.g. as in the indexing part of `&a[n]` where `n` is the array size, is not usually regarded as UB. If your argument were valid (it isn't) it would clinch this case. Furthermore, it's explicitly not UB to dereference a nullpointer in a `typeid` expression. Yet by your (incorrect) reasoning it would be. – Cheers and hth. - Alf Jan 05 '18 at 00:16
  • 2
    @Cheersandhth.-Alf I think there's something wrong with the reasoning in your objection. E.g. dereferencing a pointer one past the end of an array is absolutely UB. If `&a[n]` is well defined, there must be a special exception for it. – melpomene Jan 05 '18 at 00:32
  • @melpomene: As I recall there is such exception in C, even though C doesn't have references. It's a long standing problem, goes back all the way to 1998. Until the standard is fixed any claim that it is or clearly isn't UB (e.g. your "absolutely"), is just frivolous. – Cheers and hth. - Alf Jan 05 '18 at 00:34
0

You're better off thinking of references as little more than a handy notation for pointers.

They are still pointers, and the runtime error occurs when you use (dereference) a null pointer, not when you pass it to a function.

(An added advantage of references is that they can not be changed to reference something else, once initialized.)

Sid S
  • 6,037
  • 2
  • 18
  • 24
  • 1
    There's nothing in C++ that says dereferencing a null pointer causes a runtime error. – melpomene Jan 05 '18 at 00:47
  • No, I can see that I am perhaps a bit to "normal OS" centric in expecting that. It is after all just a memory address. Good point, actually. – JoeTaicoon Jan 05 '18 at 05:54
  • If it was treated as "just a memory address" and that address happened to be valid, it would be impossible to debug many programs. In real life, dereferencing a null pointer *has to* cause a runtime error. – Sid S Jan 05 '18 at 05:57