why this code is true??

Pages: 12
hello to all.
please somebody explain why this code is true:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <cstdlib>

using namespace std;

class Foo {
public:
    void doStuff() {
        printf("hi from Foo!\n");
    }
};

int main()
{
	Foo *f = NULL;
	f->doStuff();

	system("pause");
	return 0;
}

f has NULL value but f->doStuff(); work correctly.

(sorry,my English is noot very good)
doStuff does not use the 'this' pointer. So it doesn't matter what 'f' is because it's never used.

It's similar to doing this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Foo
{
};

void doStuff(Foo* foo)
{
    // who cares what the passed 'foo' is because
    //  it isn't used
    printf("hi from Foo!\n");
}

int main()
{
   Foo* f = 0;
   doStuff(f);  // ok

   return 0;
}


The pointer matters if:
- the function is virtual
or
- the function accesses any non-static member vars.

If the function does neither of those, then this goes unused, so it doesn't matter if the pointer is bad.
Don't take that as an endorsement to do stupid stuff, though. OO (polymorphism) will not work with NULL pointers.
Disch: Uh... really? I would bet it worked only accidently. Dereffering a null pointer is undefined behaviour (and "calling a function normally" luckily fits into the description of "undefined behaviour")

Question: Is this behaviour covered by the standard or is it just accidential that the compiler will work in this case (because of typical static function binding implementations)? I thought dereferencing a nullptr is *always* undefined behaviour, no matter whether the function call *after* the dereferencing binds dynamic or static.

If yes, what about this? Is this also allowed?

1
2
3
4
5
6
7
8
9
10
struct Foo
{
    void foo() {}
    Foo* operator->() { return 0; }
};

int main()
{
    new Foo()->foo();
}



Ciao, Imi.
This statement does not make sense:
new Foo()->foo();
No, it doesn't. :l
@imi,
I thought dereferencing a nullptr is *always* undefined behaviour


That's the point. Because there's no local member data access, there is no null pointer dereference.
+1 to what kbw said.

Unless the function is virtual or you're accessing a data member, the pointer is never used, and is therefore never dereferenced.

You can even use this in functions to check to see if the this pointer is null:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool MyClass::IsNullPtr() const
{
  if(!this)  return true;  // null pointer

  return false;
}

...

MyClass* foo = 0;
if(foo->IsNullPtr())
{
  // this code will execute
}
Sorry guys to wake up this topic again, but I am still not convinced ;-). Only because something accidently work or would make sense from the view of some compilers, doesn't make it legal C++. And only because you don't use the result of a nullptr dereference doesn't make the act of dereferencing less illegal.

1
2
3
int* i = 0;
*i;
cout << "boo!";


The code above is actually undefined behaviour, although "*i" is never used.

If you go into the realm of undefined behaviour by dereferencing a null pointer, then you may get results like the one experienced, but this is not something anyone should do by purpose (except if you are standing with the back to a deadline-wall and have a publisher-gun between your eyes ;).

So the point is still: I know that most compilers will static link any call to member functions without actually the need to dereference "this". But is this really covered by the C++ standard?

(And the next question would be: Even if it is covered, is there a good reason to use this feature other than impress people at the next C++ standup party? ;-)


Maybe it boils down to the question whether the both statements are the same or not (which I am not sure):

1
2
3
4
5
6
Foo* pFoo = 0;
foo& foo = *pFoo;
foo.doStuff();

Foo* pFoo2 = 0;
pFoo2->doStuff();


Or in other words: Is the default behaviour of operator-> to "dereference this and then call operator.() on the result" or is the default behaviour of operator-> defined somehow special as "call operator.() on the type that you would get if you would dereference the expression -> is applied (without actually dereferencing it)."?

(I know that there is no "operator.", but you get what I mean, right? ;-)


By the way: keep in mind that there is no such thing as an empty struct in C++. "sizeof(Foo)" never returns 0. "new Foo" always allocates at least one byte (never nothing). Two different instances of any struct (including empty ones) always have different addresses. This may or may not have anything to do with the case at hand... ;-)


Ciao, Imi.

PS: guestgulkan: Sorry, make it Foo()->foo();. But it probably doesn't apply to our problem here anyway.

PPS: This here is not the "empty base class optimization" - exception mentioned in the standard here. I know that base classes containing no data members do not need to be represented in memory, but here you say that "empty classes do not dereference this to call member functions".
There was no accident. The behaviour is defined and Disch explained why.

1
2
3
int* i = 0;
*i;
cout << "boo!";
is clearly defined. i is nullptr and is not dereferenced.

So the point is still: I know that most compilers will static link any call to member functions without actually the need to dereference "this". But is this really covered by the C++ standard?
Yes. Read 9.3.2 The this pointer in the standard.

You should think of non-virtual functions as scoped functions, they have no mystical powers except to access the object thru the this pointer and the ability to call protected/private methods on the class.
Last edited on
kbw: I fail to see anything in 9.3.2 that either says that dereferencing 0 is allowed for the call to non-static member functions nor that a pointer is not dereferenced at all when using -> syntax. It talks about the type of this (beeing (const/volatile) Foo* in member functions of Foo) and all restrictions that happen for the const - case.

Remember: I am saying, that already the dereference of the pointer "f" into a "Foo&" is undefined behaviour, means the state of this in any member function that may(or may not be called afterwards is irrelevant.

A quick search through the standard says in 5.2.5: "If E1 has the type "pointer to class X," then the expression E1->E2 is converted to the equivalent form (*(E1)).E2;" I am saying that the point *(E1) is already undefined behaviour and that the stuff that happen within ".E2" is hence not defined either anymore. It doesn't matter how many virtual functions E1 has or whether E1 is a struct at all or a plain type. It's enough that it is a pointer to make the whole thing undefined.


Also, "*i" means to dereference i if "i" is a pointer. And doing this on a nullptr is undefined behaviour, regardless whether the result is used or not. Can you point to anything that states otherwise or why you think "*i" it is allowed in my code?


By the way.. found some other discussion two years ago. (They are not talking about specifically "Non-Data-structs without any virtual members", but I can't see any exception in the standard for this either): http://bytes.com/topic/c/answers/643559-dereferencing-null-pointer-allowed

Ciao, Imi.
I repeat, there is no null pointer dereference.
imi: That thread does raise a very interesting point (mainly about how this situation would change when multiple inheritance is involed).

I'm not so sure about this anymore. I may have to retract my earlier statements.
kbw:
I repeat, there is no null pointer dereference.


The code under question is:

1
2
	Foo *f = NULL;
	f->doStuff();


I cited the standard, saying in 5.2.5:

If E1 has the type "pointer to class X," then the expression E1->E2 is converted to the equivalent form (*(E1)).E2;


So the code is equivalent to:

1
2
	Foo *f = NULL;
	(*(f)).doStuff();


Whereas *(f) is a dereference of a pointer. The pointer is (actually NULL, but I guess you don't think there is a difference between the statement NULL and the literal here, right?)

Hence, there is a null pointer dereference, which is undefined behaviour. This was now as clear as I could put it. ;-)

I was starting this thread quite unsure, but now I am quite confident of my conclusions. Since you disagree, could you please tell me where you think my mistake in my argumentation is? Do you think 5.2.5 does not apply in this case here? If so, why not?

I completely agree with you that there is no dereferencation within the function "doStuff", but that's not the problem. The dereference happens before the function is called at all.

And I am not saying every "undefined behaviour" leads to a segfault (would be much easier to spot, if it were the case).



Disch:
That thread does raise a very interesting point (mainly about how this situation would change when multiple inheritance is involed).


Actually I think it is the same "undefined behaviour" than it is for any other class or class hierarchy. It may matter in the behaviour of certain compiler versions using certain optimization settings and certain specific platforms together with certain libraries, but I really would not recommend to write code with undefined behaviour intentionally.


Ciao, Imi.
So the real question is, is doing (*p) the same as using the memory pointed to by p no matter what standard? Or is that memory only used when a data member of the class is used, and not just a code member?
In other words, is there a compiler that can make this crash, and would such a compiler be standard-compliant?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

struct A{
	int nil;
	void f(){
		std::cout <<"lol\n";
	}
};

int main(){
	A *a=(A *)0xDEADBEEF;
	a->f();
	(*a).f();
	return 0;
}
Last edited on
helios: dead beef? lol.

Well, since for the pure C++ standard complaint question "(A*)0xDEADBEEF" is already "undefined behaviour", everything below this line is out of question already. ;-)

As far as I remember, the standard doesn't permit casts from non-0 int's to any pointer.


I still argue that this is already "undefined behaviour":

1
2
int *i = 0;
*i;



To get back to the practical world out there, once again: That something is "undefined behaviour" does not mean it won't work with practically all compilers out there. The example with the deadbeef is a good one, as e.g. if VC suddenly would segfault when you cast an int to a pointer, then a LOT of windows code would have BIG problems...

But if you already know about undefined behaviour and it's not crystal clear and tolerable, don't blame anyone when e.g. just changing /Os to /O2 suddenly kills your application.


Ciao, Imi.
Well, since for the pure C++ standard complaint question "(A*)0xDEADBEEF" is already "undefined behaviour", everything below this line is out of question already. ;-)

As far as I remember, the standard doesn't permit casts from non-0 int's to any pointer.
You're missing the point. a can be understood to mean any invalid pointer.

You didn't really answer my question.
I still argue that this is already "undefined behaviour":
That's a very weak statement. Something is either defined or undefined. There's no such thing as "arguably undefined".

The question, once again, is: is the statement *p; supposed to use the memory pointed to by p no matter what (causing undefined behavior if p is invalid), is whether the memory will be used defined, or is the behavior of the statement itself undefined?
Last edited on
@helios,
is the statement *p; supposed to use the memory pointed to by p no matter what

Of course it is. It will compile even if p points to NULL. Even if you try to reference memory that doesn't exist. It will still compile. The CPU will decide that you can't access that memory (unless it's at a valid address and you have permission to access it) and generate a general protection fault.

Edit:
is whether the memory will be used defined, or is the behavior of the statement itself undefined?

The behaviour of the statement is defined. The CPU's action is also defined: if you're allowed to access that memory location and it exists, then full steam ahead. Otherwise, you get an exception, the CPU runs the handler for it, and the OS probably kills your process.
Last edited on
The behaviour of the statement is defined. The CPU's action is also defined: if you're allowed to access that memory location and it exists, then full steam ahead. Otherwise, you get an exception, the CPU runs the handler for it, and the OS probably kills your process.


oookkaayy... I think we should clarify some terminology here. Whenever I say "undefined behaviour", I mean actually quite defined term used in the C++ standard. It means something along: "The compiler is free to generate code that behaves in any way and still can be called standard-complaint, should this case ever happen at runtime. The code even does not need to behave deterministic, means it does the same thing when run multiple times." (without the second sentence, IIRC it is called "vendor specific behaviour").



You're missing the point. a can be understood to mean any invalid pointer.


Oh, sorry. But even in this case, it is still undefined behaviour. Dereferencing an invalid pointer (e.g. uninitialized or pointing to a deleted object) is undefined behaviour (as in the definition above), regardless whether the compiler need to access any memory location or whether the resulting program will contain any code for the statement in question at all.


The question, once again, is: is the statement *p; supposed to use the memory pointed to by p no matter what (causing undefined behavior if p is invalid), is whether the memory will be used defined, or is the behavior of the statement itself undefined?


I doubt the standard will say anything about "use the memory". It clearly says that the statement itself yield in "undefined behaviour".

But since most compilers (in most optimization settings on most platforms using most libraries etc..) will not need to generate any read access to any memory when generating code for a->f();, the actual way this "undefined behaviour" will behave in praxis almost all of the time is: Just call the stupid function.

(Respective for *p;, it will most probably do not leave any machine code in the executable at all..)


Ciao, Imi.
I know what the term "undefined behaviour" means. I don't see how that's relevant to what I said. The process should terminate when the CPU generates a general protection fault when you try to reference non-existant memory. What the compiler decides to do may or may not be defined by ISO C++; but what the CPU does is defined by Intel. That's all I was saying.
Pages: 12