2

In short, is this undefined behavior?

struct C { int Fn() { return 42; }};

int main() {
  C *c = nullptr;
  return c->Fn();
}

An important detail is that C::Fn never does anything with the this pointer (explicitly or implicitly). I know empirically that some compilers generate code that does exactly what I want in this case but if this tickles UB I can't count on that staying true.

The only related question I've found is this one that would be relevant but for my case not using references.


Edit: this is highly relevant in cases like the following:

void Foo(int len) {
  if (len > 1) {
    // This can legally be assumed to be dead code;
  }

  C c;
  C* a[2] = {&c, nullptr};

  for (int i = 0; i < len ; i++) a[i]->Fn();
} 
Community
  • 1
  • 1
BCS
  • 75,627
  • 68
  • 187
  • 294
  • 2
    Yes - the answer here is pretty good: http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-beha?rq=1 – Shaggi Mar 16 '14 at 01:35
  • Examine the definition of what `->` does. – Yakk - Adam Nevraumont Mar 16 '14 at 01:35
  • Behavior is always the same. As long as the `Fn` does not try to access any members of the `class C` it will work. – Grzegorz Mar 16 '14 at 01:36
  • 1
    @Grzegorz, Maybe practically, but not in theory. – chris Mar 16 '14 at 01:39
  • @chris - Why not? `c->Fn()` translates to '&C:Fn( c )`, and what `Fn()` does with `c` (which is `this` in our case) is all that matters. – Grzegorz Mar 16 '14 at 01:44
  • 2
    @Grzegorz: If it's UB, then the compiler is free to assume (without proof) that the code is unreachable and optimize (generally by dead-code-eliminating) it's way up through the program. – BCS Mar 16 '14 at 01:45
  • 1
    `Fn` isn't visible in any case, it's `private` – Paul Evans Mar 16 '14 at 01:46
  • @BCS - why do you think the compiler would assume the code is not reachable? – Grzegorz Mar 16 '14 at 01:47
  • 2
    Because if it's reached then it's UB and there are no restrictions on what the program does. The compiler can assume it can never be reached and generate a program that is still correct (because the assumption holds in the one case and because anything goes in the other). -- There have been security bugs from compilers actually doing this. – BCS Mar 16 '14 at 01:50
  • @BCS - In you addition don't you want to call `a[i]->Fn(i)` ? – Grzegorz Mar 16 '14 at 01:51
  • 9
    "It's *technically* undefined but it *should* always work" is a recipe for disaster, in my experience – M.M Mar 16 '14 at 02:00
  • 1
    @MattMcNabb I agree. But just for fun: how do you use signed integers? ;-) – stefan Mar 17 '14 at 13:53
  • @stefan In way that can't overflow. – BCS Mar 17 '14 at 17:42
  • @BCS So you check every single addition and multiplication? Wow, that must be exhausting. – stefan Mar 17 '14 at 17:43
  • 1
    I do ignore that rule for `unsigned char` to `char` conversions though.. it's just too annoying to cater to the possibility that this could trap. – M.M Mar 18 '14 at 02:45
  • @stefan, tl;dr; don't write code that *depends* on UB happening. -- That at least can be trivially enforced via things like `-fsanitize=undefined` (or the integer subset there of). -- To be more rigorous, if you know what range trusted inputs are in and check un-trusted ones, then it's often possible to statically show for correct code that no overflows happen. Generally it's actually rather easy as long as your have large "safety margins" in the size of your integers. – BCS Mar 18 '14 at 18:54

1 Answers1

0

Using N3797, S9.3.1:

If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

Nothing about 'pointer to type'. I read in this that the object found by dereferencing the pointer must be of type X, which null is not. The dereferencing is found in 5.2.5:

For the first option (dot) the first expression shall have complete class type. For the second option (arrow) the first expression shall have pointer to complete class type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 5.2.5 will address only the first option (dot).67

If you ever touch the this pointer you run into S5.1.1/1:

The keyword this names a pointer to the object for which a non-static member function (9.3.2) is invoked or a non-static data member’s initializer (9.2) is evaluated.

My vote is UB for any call with anything other than a valid pointer to an object. Null is UB.

david.pfx
  • 10,520
  • 3
  • 30
  • 63