Mistakenly I wrote something daft, which to my surprise worked.
class A
{ public:
void print()
{
std::cout << "You can't/won't see me !!!" << std::endl;
}
A* get_me_the_current_object()
{
return this;
}
};
int main()
{
A* abc = dynamic_cast<A*>(abc);
abc->print();
}
Here, A* abc = dynamic_cast<A*>(abc), I am doing dynamic_cast on a pointer, which isn't declared. But, it works, so I assumed that the above statement is broken as:
A* abc;
abc = dynamic_cast<A*>(abc);
and therefore, it works. However, on trying some more weird scenarios such as:
A* abc;
abc->print();
and, further
A* abc = abc->get_me_the_current_object();
abc->print();
I was flabbergasted, looking at how these examples worked and the mapping was done.
Can someone please elaborate on how these are working? Thanks in advance.
You've made the common mistake of thinking that undefined behaviour and C++ bugs mean you should expect to see a dramatic crash or your computer catching fire. Sometimes nothing happens. That doesn't mean the code "works", because it's still got a bug, it's just that the symptoms haven't shown up ... yet.
But, it works, so I assumed that the above statement is broken as:
Yes, all you're doing is converting an uninitialized pointer to the same type, i.e. no conversion needed, so the compiler does nothing. Your pointer is still the same type and is still uninitialized.
This is similar to this:
int i = i;
This is valid according to the grammar of C++, because i is in scope at that point, but is undefined because it copies an uninitialized object. It's unlikely to actually set your computer on fire though, it appears to "work".
Can someone please elaborate on how these are working?
Technically you're dereferencing an invalid pointer, which is undefined behaviour, but since your member functions don't actually use any members of the object, the invalid this pointer is not dereferenced, so the code "works" (or at least appears to.)
This is similar to:
void function(A* that)
{
std::cout << "Hello, world!\n";
}
A* p;
function(p);
Because the that pointer is not used (like the this pointer is not used in your member functions) this doesn't necessarily crash, although it might do on implementations where even copying an uninitialized pointer could cause a hardware fault. In your example it seems that your compiler doesn't need to dereference abc to call a non-static member function, and passing it as the hidden this parameter does not cause a hardware fault, but the behaviour is still undefined even though it doesn't fail in an obvious way such as a segfault..
abc is uninitialized and points to an undefined location in memory, but your methods don't read anything from *this so they won't crash.
The fact that they won't crash is almost certainly implementation defined behavior though.
Related
The fllowing questions are:
p->test() should not work after b is destroyed. However, the code is running without any issue, the dynamic binding still works;
when the destructor of A is defined, the dynamic binding doesnot work anymore. What is the logic behind it?
#include <iostream>
using namespace std;
struct A {
//~A() {}
virtual void test() { cout << 0 << endl; }
};
class B :public A {
void test() { cout << 1 << endl; }
};
int main() {
A* p;
{
B b;
p = &b;
}
p->test(); // the method called will be different if the destructor of A is removed
}
p->test() should not work after b is destroyed. However, the code is running without any issue, the dynamic binding still works;
It does not "work". p->test() invokes undefined behavior.
when the destructor of A is defined, the dynamic binding doesnot work anymore. What is the logic behind it?
There is no logic behind it, other than implementation details of the compiler you are using, because the C++ standard does not mandate what a compiler should do with code that has undefined behavior.
For more details on undefined behavior I refer you to https://en.cppreference.com/w/cpp/language/ub
Compilers cannot detect all undefined behavior, but some of it. With gcc you can try -fsanitize=address to see the issue more clearly: https://godbolt.org/z/qxTs4sxcW.
Welcome to the world of Undefined Behaviour! Any access to a deleted object, including calling a method on it invokes undefined behaviour.
That means that the languages requires no specific behaviour, and depending on the internals of the compiler and possibly on any apparently unrelated thing on the computer anything can happen from the expected behaviour (you experienced it) to an immediate crash or unexpected results occuring immediately or later.
Never try to test the result of an UB operation: it can change from one compilation to a new one even with the same configuration or even from one run to the other if it depends on uninitialized memory.
Worse: if the compiler can detect UB in a portion of code, it can optimize out that portion because the language allows it to assume that a program should never invoke UB.
TL/DR: you are invoking UB here. Don't. Just don't.
why is this code practically reliably working, is not unstable, undefined?
it's dereferencing unallocated, dangling pointers to objects.
thanks.
#include <iostream>
using namespace std;
class Base{
public:
void vf()
{
cout<<"vf\n";
}
void pr(){
cout<<"vb\n";
}
};
int main() {
Base* b ;
//Base * bp = new Base; //trying to disrupt memory
char ar[]= "ttttttttttt"; //trying to disrupt memory
b->pr();
char aa[]= "ttttttttttt";
b->vf();
return 0;
}
Welcome to the Wonderful World of Undefined Behavior! According to the C++ spec, the behavior of this problem is undefined, so what you're seeing might work on your system but crash on others, or vice-versa.
Practically speaking, what's happening here is probably an artifact of how the compiler generates code for member functions. Typically, a member function that looks like this:
void doSomething() {
cout << "Hello!" << endl;
}
would probably be compiled as if it were a free function like this:
void Base_doSomething(Base* this) {
cout << "Hello!" << endl;
}
In that sense, when you write
bf->doSomething();
the compiler treats it as if you've written
Base_doSomething(bf);
and nothing bad happens, because Base_doSomething doesn't reference the this pointer, which is where all the bad things happen.
Now, this is very brittle. If you try to read any of the data members of the Base type, this code will crash because then you are reading from a bad pointer. Similarly, if doSomething were a virtual function, you'd get a crash because calling a virtual function (typically) requires reading from the receiver object to find the vtable to determine which function to call, and a dangling pointer will then lead to a bad memory access.
So, to summarize:
This code leads to undefined behavior, so the particular behavior you're seeing isn't guaranteed to happen or work across platforms.
Although syntactically bp->doSomething() looks like a pointer dereference, it might not actually involve a pointer dereference.
Member functions are typically compiled as free functions that have an "implicit this" pointer passed as a first argument.
Virtual functions work differently than this, so you'd expect to see different behavior there.
Hope this helps!
Here is my code.
class IService {
};
class X_Service {
public:
void service1() {
std::cout<< "Service1 Running..."<<std::endl;
}
};
int main() {
IService service;
auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
(service.*(func))();
return 0;
}
I don't understand how this works. I didn't inherit IService and didn't create a X_Service object but it works.
Can someone explain this?
Your confusion probably comes from the misunderstanding that because something compiles and runs without crashing, it "works". Which is not really true.
There are many ways you can break the rules of the language and still write code that compiles and runs. By using reinterpret_cast here and making an invalid cast you have broken the rules of the language, and your program has Undefined Behaviour.
That means it can seem to work, it can crash or it can just do something completely different from what you intended.
In your case it seems to work, but it's still UB and the code is not valid.
Under the hood your compiler will turn all these functions into machine code that is basically just jumps to certain addresses in memory and then executing commands stored there.
A member function is just a function that has in addition to local variables and parameters, a piece of memory that stores the address of the class object. That piece of memory holds the address you are accessing when you use the this keyword.
If you call a member function on a wrong object or nullptr, then you basically just make the this pointer point to something invalid.
Your function doesn't access this, which is the reason your program doesn't blow up.
That said, this is still undefined behavior, and anything could happen.
So, I had some fun and manipulated the code a bit. This is also an empirical answer. There are a lot of pitfalls that risk stack corruption with this way of doing things, so I changed the code a bit to make it to where stack corruption does not occur but kind of show what it happening.
#include <iostream>
class IService {
public:
int x;
};
class X_Service {
public:
int x;
void service1() {
this->x = 65;
std::cout << this->x << std::endl;
}
};
int main() {
IService service;
auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
(service.*(func))();
std::cout << service.x << std::endl;
std::cin.get();
X_Service derp;
(derp.service1)();
std::cout << derp.x << std::endl;
return 0;
}
So from the outset, auto gave you the power to make a none type safe pointer void (IService::*)()also the instance of the object itself is this-> regardless of what member function of whatever class you are stealth inheriting from. The only issue is that the first variable of the instance is interpreted based on the first variable of the class you are stealth inheriting from, which can lead to stack corruption if the type differs.
Ways to get cool output but inevitably cause stack corruption, you can do the following fun things.
class IService {
public:
char x;
};
Your IDE will detect stack corruption of your IService object, but getting that output of
65
A
is kind of worth it, but you will see that issues will arise doing this stealth inheritance.
I'm also on an 86x compiler. So basically my variable are all lined up. Say for instance if I add an int y above int x in Iservice, this program would output nonsense. Basically it only works because my classes are binary compatible.
When you reinterpret_cast a function or member function pointer to a different type, you are never allowed to call the resulting pointer except if you cast it back to its original type first and call through that.
Violating this rule causes undefined behavior. This means that you loose any language guarantee that the program will behave in any specific way, absent additional guarantees from your specific compiler.
reinterpret_cast is generally dangerous because it completely circumvents the type system. If you use it you need to always verify yourself by looking at the language rules, whether the cast and the way the result will be used is well-defined. reinterpret_cast tells the compiler that you know what you are doing and that you don't want any warning or error, even if the result will be non-sense.
So I'm trying to learn a bit more about the differences between C-style-casts, static_cast, dynamic_cast and I decided to try this example which should reflect the differences between C-style-casts and static_cast pretty good.
class B
{
public:
void hi() { cout << "hello" << endl; }
};
class D: public B {};
class A {};
int main()
{
A* a = new A();
B* b = (B*)a;
b->hi();
}
Well this code snippet should reflect that the C-style-cast goes very wrong and the bad cast is not detected at all. Partially it happens that way. The bad cast is not detected, but I was surprised when the program, instead of crashing at b->hi();, it printed on the screen the word "hello".
Now, why is this happening ? What object was used to call such a method, when there's no B object instantiated ? I'm using g++ to compile.
As others said it is undefined behaviour.
Why it is working though? It is probably because the function call is linked statically, during compile-time (it's not virtual function). The function B::hi() exists so it is called. Try to add variable to class B and use it in function hi(). Then you will see problem (trash value) on the screen:
class B
{
public:
void hi() { cout << "hello, my value is " << x << endl; }
private:
int x = 5;
};
Otherwise you could make function hi() virtual. Then function is linked dynamically, at runtime and program crashes immediately:
class B
{
public:
virtual void hi() { cout << "hello" << endl; }
};
This only works because of the implementation of the hi() method itself, and the peculiar part of the C++ spec called undefined behaviour.
Casting using a C-style cast to an incompatible pointer type is undefined behaviour - literally anything at all could happen.
In this case, the compiler has obviously decided to just trust you, and has decided to believe that b is indeed a valid pointer to an instance of B - this is in fact all a C-style cast will ever do, as they involve no runtime behaviour. When you call hi() on it, the method works because:
It doesn't access any instance variables belonging to B but not A (indeed, it doesn't access any instance variables at all)
It's not virtual, so it doesn't need to be looked up in b's vtable to be called
Therefore it works, but in almost all non-trivial cases such a malformed cast followed by a method call would result in a crash or memory corruption. And you can't rely on this kind of behaviour - undefined doesn't mean it has to be the same every time you run it, either. The compiler is perfectly within its rights with this code to insert a random number generator and, upon generating a 1, start up a complete copy of the original Doom. Keep that firmly in mind whenever anything involving undefined behaviour appears to work, because it might not work tomorrow and you need to treat it like that.
Now, why is this happening ?
Because it can happen. Anything can happen. The behaviour is undefined.
The fact that something unexpected happened demonstrates well why UB is so dangerous. If it always caused a crash, then it would be far easier to deal with.
What object was used to call such a method
Most likely, the compiler blindly trusts you, and assumes that b does point to an object of type B (which it doesn't). It probably would use the pointed memory as if the assumption was true. The member function didn't access any of the memory that belongs to the object, and the behaviour happened to be the same as if there had been an object of correct type.
Being undefined, the behaviour could be completely different. If you try to run the program again, the standard doesn't guarantee that demons won't fly out of your nose.
I'm looking for clarification on this bit of code. The call to A::hello() works (I expected a segv). The segfault does come through on the access to member x, so it seems like the method resolution alone doesn't actually dereference bla?
I compiled with optimization off, gcc 4.6.3. Why doesn't bla->hello() blow up? Just wonderin' what's going on. Thanks.
class A
{
public:
int x;
A() { cout << "constructing a" << endl; }
void hello()
{
cout << "hello a" << endl;
}
};
int main()
{
A * bla;
bla = NULL;
bla->hello(); // prints "hello a"
bla->x = 5; // segfault
}
Your program exhibits undefined behavior. "Seems to work" is one possible manifestation of undefined behavior.
In particular, bla->hello() call appears to work because hello() doesn't actually use this in any way, so it just happens not to notice that this is not a valid pointer.
You are dereferencing NULL pointer, i.e. trying to access object stored at address NULL:
bla = NULL;
bla->hello();
bla->x = 5;
which results in undefined behavior, which means that anything can happen, including the seg fault while assigning 5 to the member x and also including deceptive "works as expected" effect while invoking the hello method.
At least in the typical implementation, when you call a non-virtual member function via a pointer, that pointer is not dereferenced to find the function.
The type of the pointer is used to determine the scope (context) in which to search for the name of the function, but that happens entirely at compile time. What the pointer points at (including "nothing", in the case of a null pointer) is irrelevant to finding the function itself.
After the compiler finds the correct function, it typically translates a call like a->b(c); into something roughly equivalent to: b(a, c); -- a then becomes the value of this inside the function. When the function refers to data in the object, this is dereferenced to find the correct object, then an offset is normally applied to find the correct item in that object.
If the member function never attempts to use any of the object's members, the fact that this is a null pointer doesn't affect anything.
If, on the other hand, the member function does attempt to use a member of the object, it'll attempt to dereference this to do that, and if this is a NULL pointer, that won't work. Likewise, calling a virtual function always uses the vtable pointer, which is in the object, so attempting to call a virtual function via a null pointer can be expected to fail (regardless of whether the code in the virtual function refers to data in the object or not).
As I said to start with, I'm talking about the typical implementation here. From the viewpoint of the standard, you simply have undefined behavior, and that's the end of it. In theory, an implementation doesn't have to work the way I've described. In reality, however, essentially all the reasonably popular implementations of C++ (e.g., MS VC++, g++, Clang, and Intel) all work very similarly in these respects.
As long as a member function doesn't dereference this, it's usually "safe" to call it, but you're then in undefined behavior land.
In theory, this is undefined behavior. In practice, you do not use this pointer when calling hello(), it does not reference your class at all, and therefore works and does not generate memory access violation. When you do bla->x, however, you are trying to reference a memory though bla pointer which is uninitialized, and it crashes. Again, even in this case there is no guarantee that it will crash, this is undefined behavior.