Accessing Class Members on a Null Pointer

Accessing class members on a NULL pointer

The object foo is a local variable with type Foo*. That variable likely gets allocated on the stack for the main function, just like any other local variable. But the value stored in foo is a null pointer. It doesn't point anywhere. There is no instance of type Foo represented anywhere.

To call a virtual function, the caller needs to know which object the function is being called on. That's because the object itself is what tells which function should really be called. (That's frequently implemented by giving the object a pointer to a vtable, a list of function-pointers, and the caller just knows it's supposed to call the first function on the list, without knowing in advance where that pointer points.)

But to call a non-virtual function, the caller doesn't need to know all that. The compiler knows exactly which function will get called, so it can generate a CALL machine-code instruction to go directly to the desired function. It simply passes a pointer to the object the function was called on as a hidden parameter to the function. In other words, the compiler translates your function call into this:

void Foo_say_hi(Foo* this);

Foo_say_hi(foo);

Now, since the implementation of that function never makes reference to any members of the object pointed to by its this argument, you effectively dodge the bullet of dereferencing a null pointer because you never dereference one.

Formally, calling any function — even a non-virtual one — on a null pointer is undefined behavior. One of the allowed results of undefined behavior is that your code appears to run exactly as you intended. You shouldn't rely on that, although you will sometimes find libraries from your compiler vendor that do rely on that. But the compiler vendor has the advantage of being able to add further definition to what would otherwise be undefined behavior. Don't do it yourself.

Accessing members of class using nullptr in C++

According to my knowledge, we can access the members of the class through pointers by making pointer to the class and then store the address of an object of that class type, and then we can access the members through '->' using that pointer

That is correct. Though, unless you need to, you would not use a pointer, but just the object itself:

Check ptr;
ptr.print();

How and why is it working without any error ...

You do not get an error, because dereferencing a null pointer is undefined behavior. Compilers may emit a warning in cases they can detect the problem, but they are not required to issue an error (and in general they cannot).

... or undefined behaviour even though i did not store an address of object in it

You code does have undefined behavior. Undefined behavior means that you get no guarantee that your code will do the right thing. You do not get a guarantee that your code will do something wrong.

Behind the scenes the this pointer is passed as implicit parameter to member functions. As print is not actually using any members of the class, the pointer isn't used and your code appears to work. However, this is just by chance. By the rules of the language ptr->print(); is already undefined behavior (no matter if print does use members or not).

c++ access static members using null pointer

TL;DR: Your example is well-defined. Merely dereferencing a null pointer is not invoking UB.

There is a lot of debate over this topic, which basically boils down to whether indirection through a null pointer is itself UB.

The only questionable thing that happens in your example is the evaluation of the object expression. In particular, d->a is equivalent to (*d).a according to [expr.ref]/2:

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).

*d is just evaluated:

The postfix expression before the dot or arrow is evaluated;65 the
result of that evaluation, together with the id-expression, determines
the result of the entire postfix expression.

65) If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary
to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

Let's extract the critical part of the code. Consider the expression statement

*d;

In this statement, *d is a discarded value expression according to [stmt.expr]. So *d is solely evaluated1, just as in d->a.

Hence if *d; is valid, or in other words the evaluation of the expression *d, so is your example.

Does indirection through null pointers inherently result in undefined behavior?

There is the open CWG issue #232, created over fifteen years ago, which concerns this exact question. A very important argument is raised. The report starts with

At least a couple of places in the IS state that indirection through a
null pointer produces undefined behavior: 1.9 [intro.execution]
paragraph 4 gives "dereferencing the null pointer" as an example of
undefined behavior, and 8.3.2 [dcl.ref] paragraph 4 (in a note) uses
this supposedly undefined behavior as justification for the
nonexistence of "null references."

Note that the example mentioned was changed to cover modifications of const objects instead, and the note in [dcl.ref] - while still existing - is not normative. The normative passage was removed to avoid commitment.

However, 5.3.1 [expr.unary.op] paragraph 1, which describes the unary
"*" operator, does not say that the behavior is undefined if the
operand is a null pointer, as one might expect. Furthermore, at least
one passage gives dereferencing a null pointer well-defined behavior:
5.2.8 [expr.typeid] paragraph 2 says

If the lvalue expression is obtained by applying the unary * operator
to a pointer and the pointer is a null pointer value (4.10
[conv.ptr]), the typeid expression throws the bad_typeid exception
(18.7.3 [bad.typeid]).


This is inconsistent and should be cleaned up.

The last point is especially important. The quote in [expr.typeid] still exists and appertains to glvalues of polymorphic class type, which is the case in the following example:

int main() try {

// Polymorphic type
class A
{
virtual ~A(){}
};

typeid( *((A*)0) );

}
catch (std::bad_typeid)
{
std::cerr << "bad_exception\n";
}

The behavior of this program is well-defined (an exception will be thrown and catched), and the expression *((A*)0) is evaluated as it isn't part of an unevaluated operand. Now if indirection through null pointers induced UB, then the expression written as

*((A*)0);

would be doing just that, inducing UB, which seems nonsensical when compared to the typeid scenario. If the above expression is merely evaluated as every discarded-value expression is1, where is the crucial difference that makes the evaluation in the second snippet UB? There is no existing implementation that analyzes the typeid-operand, finds the innermost, corresponding dereference and surrounds its operand with a check - there would be a performance loss, too.

A note in that issue then ends the short discussion with:

We agreed that the approach in the standard seems okay: p = 0; *p;
is not inherently an error.
An lvalue-to-rvalue conversion would give
it undefined behavior.

I.e. the committee agreed upon this. Although the proposed resolution of this report, which introduced so-called "empty lvalues", was never adopted…

However, “not modifiable” is a compile-time concept, while in fact
this deals with runtime values and thus should produce undefined
behavior instead. Also, there are other contexts in which lvalues can
occur, such as the left operand of . or .*, which should also be
restricted. Additional drafting is required.

that does not affect the rationale. Then again, it should be noted that this issue even precedes C++03, which makes it less convincing while we approach C++17.


CWG-issue #315 seems to cover your case as well:

Another instance to consider is that of invoking a member function
from a null pointer:

  struct A { void f () { } };
int main ()
{
A* ap = 0;
ap->f ();
}

[…]

Rationale (October 2003):

We agreed the example should be allowed. p->f() is rewritten as
(*p).f() according to 5.2.5 [expr.ref]. *p is not an error when
p is null unless the lvalue is converted to an rvalue (4.1
[conv.lval]), which it isn't here.

According to this rationale, indirection through a null pointer per se does not invoke UB without further lvalue-to-rvalue conversions (=accesses to stored value), reference bindings, value computations or the like. (Nota bene: Calling a non-static member function with a null pointer should invoke UB, albeit merely hazily disallowed by [class.mfct.non-static]/2. The rationale is outdated in this respect.)

I.e. a mere evaluation of *d does not suffice to invoke UB. The identity of the object is not required, and neither is its previously stored value. On the other hand, e.g.

*p = 123;

is undefined since there is a value computation of the left operand, [expr.ass]/1:

In all cases, the assignment is sequenced after the value computation
of the right and left operands

Because the left operand is expected to be a glvalue, the identity of the object referred to by that glvalue must be determined as mentioned by the definition of evaluation of an expression in [intro.execution]/12, which is impossible (and thus leads to UB).


1 [expr]/11:

In some contexts, an expression only appears for its side effects.
Such an expression is called a discarded-value expression. The
expression is evaluated and its value is discarded.
[…]. The lvalue-to-rvalue conversion (4.1) is
applied if and only if the expression is a glvalue of
volatile-qualified type and […]

Calling class method through NULL class pointer

Under the hood most compilers will transform your class to something like this:

struct _ABC_data{  
int a ;
};
// table of member functions
void _ABC_print( _ABC_data* this );

where _ABC_data is a C-style struct
and your call ptr->print(); will be transformed to:

_ABC_print(nullptr)

which is alright while execution since you do not use this arg.


UPDATE: (Thanks to Windows programmer for right comment)

Such code is alright only for CPU which executes it.

There is absolutely positively no sane reason to exploit this implementation feature. Because:

  1. Standard states it yields undefined behavior
  2. If you actually need ability to call member function without instance, using static keyword gives you that with all the portability and compile-time checks

NULL pointer able to access class member function

Dereferencing a NULL pointer, as you do in your code, is undefined behavior. One of the possibilities of such undefinedness is that it may just work. But it may crash the next time, or do something totally unexpected.

Since you are not using the implicit this argument in your member function print, seems like such NULL pointer never needs to actually be dereferenced.

Pointer to a class access public member function

You have to know, how the methods are called.

ex::show();
(...)

obj->show();

Is (mostly probably) translated by your specific compiler to:

show(ex * this);
(...)

show(obj);

And since you do not use this inside, there is no reason for the exception to be thrown...

I stressed out your specific compiler, because:

C++ standard, chapter 8.3.2, References

[ 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. As described in 9.6, a reference cannot be bound directly to a bit-field.
—end note ]

Is accessing static class member via unitilialized pointer UB?

The semantics of a->n are that *a is evaluated, but not accessed, since the data member is static. See C++17 [expr.ref]:

... The postfix expression before the dot or arrow is evaluated ... The expression
E1->E2 is converted to the equivalent form (*(E1)).E2 ...

There is also a footnote that says:

If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

In this case, the expression *a is evaluated. Since a is an automatic variable that has not been initialized, [dcl.init]/12 applies:

If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases: [ ... ]

Evaluating *a clearly requires accessing the value of the pointer variable a, which is an indeterminate value, therefore it is UB.



Related Topics



Leave a reply



Submit