1

I came across a very strange behaviour using G++ (4.5.2) on different platforms; here's the code :

class Class
{
private:
  std::string rString;

public:
  Class()
  {
    this->rString = "random string";
    std::cout << "Constructor of Class" << std::endl;
  }

  virtual ~Class()
  {
    std::cout << "Destructor of Class" << std::endl;
  }

  void          say() const
  {
    std::cout << "Just saying ..." << std::endl;
    if (this == NULL)
      std::cout << "Man that's really bad" << std::endl;
  }

  void          hello() const
  {
    std::cout << "Hello " << this->rString << std::endl;
  }

};


int     main()
{
  Class *c = NULL;

  /* Dereferencing a NULL pointer results
     in a successful call to the non-static method say()
     without constructing Class */
  (*c).say(); // or c->say()

  /* Dereferencing a NULL pointer and accessing a random
     memory area results in a successful call to say()
     as well */
  c[42000].say();

  /* Dereferencing a NULL pointer and accessing a
     method which needs explicit construction of Class
     results in a Segmentation fault */
  c->hello();

  return (0);
}

The question is, why the two first statements in the main function don't crash the program? Is this undefined behaviour, or the compiler is simply calling Class::say() as if it was static since it doesn't dereference "this" pointer inside the method?

Halim Qarroum
  • 13,985
  • 4
  • 46
  • 71
  • Your edit was completely unnecessary, and pretty misguided: `inline` has no effect here because functions that are defined inside a class are automatically `inline`. – Konrad Rudolph Mar 19 '13 at 10:23
  • You're wrong, the compiler may or may not `inline` these methods in the binary file he will generate. Just because these methods are defined and implemented within the class `Class` does not automatically mean they will actually be inlined. Plus, prepending my prototypes with the `inline` keyword does not either mean this code will be inlined by the compiler. I'm just giving a hint to the compiler saying that generating a prolog for each of these methods might be heavy. – Halim Qarroum Mar 19 '13 at 13:44
  • No, *you* are wrong. You picked up some half-information that is largely right, but not completely. [Member functions that are defined (as opposed to just declared) inside a class are automatically `inline`](http://msdn.microsoft.com/en-us/library/bw1hbe6y(v=vs.80).aspx) (§7.1.2/3 of the standard). Note that `inline` has more effects than just advising the compiler to perform call inlining. §7.1.2 has more details. – Konrad Rudolph Mar 19 '13 at 14:02
  • I was sure of the opposit. Anyway, marking these methods as inline is not, at the end, totally `unnecessary, and pretty misguided` as they've reminded me of this aspect of the C++ standard. – Halim Qarroum Mar 19 '13 at 14:35

5 Answers5

10

Yes, it's undefined behavior. You cannot call a member function with a null pointer.

In practice, the first two indeed work because this is never dereferenced so your undefined behavior doesn't have to manifest like it does in the third, where memory is indeed wrongly accessed.

(In all cases, you die a little inside each time it's called, so don't do that.)

Community
  • 1
  • 1
GManNickG
  • 494,350
  • 52
  • 494
  • 543
6

Undefined behavior just means what happens is not defined. It doesn't mean "crash."

Undefined behavior can do anything at all, including work the way you intend it to.

OmnipotentEntity
  • 16,531
  • 6
  • 62
  • 96
1

This is undefined behavior. Since you're not using functions as "virtual" this will just call the functions statically.

Aniket Inge
  • 25,375
  • 5
  • 50
  • 78
1

It's undefined behavior, though it would appear in the first two instances it's been optimized to call Class::say() without using any member variables of the object itself (hence this-> not being dereferenced/used causing a sigserv), but the 3rd attempts to access it's member. Likewise, the following may seg fault on another compiler like VC++'s.

M4rc
  • 473
  • 2
  • 13
0

Internally, the compiler implements calls to non-static, non-virtual functions by simply passing the object pointer this as additional parameter or rather together with all the proper parameters on the stack.

The standard doesn't mandate what is supposed to happen when a member function is called with something that is not a memory location holding an actual object of the respective class or struct. Requiring this would require non-noop runtime checks (if possible at all) and is therefore not reasonable to ask from a conforming implementation.

The typing system already makes sure you have go out of your way to call a member function with an object of a wrong type, but checking against NULL or nullptr calls cannot meaningfully be covered by the pointer type.
If you want your binary to crash when a member function is called with the null pointer, making the member function virtual should do the job, because the compiler will have to dereference the vptr of this and the operating system will respond by showing you the finger.

bitmask
  • 32,434
  • 14
  • 99
  • 159