Polymorphism Without Indirect Jumps?
When I learned MIPS assembly a few months ago, a question popped into my head that I forgot to ask then, so I thought I'd ask it now:
Is it possible to implement polymorphism without indirect jump instructions? If so, how?
(Indirect jumps are the "jump register" instructions, e.g. jr $t0
in MIPS or jmp EAX
in x86.)
One such solution I came up with is self-modifying cod开发者_如何学编程e, but I'm curious as to whether polymorphism (and so OOP) would be possible by any other means.
The simplest answer to your question would be to write your program (and possible assembler) in such a way that all method calls can be resolved at runtime thus negating the need for a lookup table. I'm going to assume you're talking about passing a subclass to a function that was designed for a superclass and it is therefore impossible to implement this optimization.
Eliminating the lookup, I think, is outside the scope of your question so I'm going to suggest replacements for the jmp <reg>
instruction (sorry, I only know x86).
- You can execute a
call <mem>
on a memory address (isn't this how you would do it using a lookup table?) - You can execute a
call <reg>
on a register (not entirely different from the jmp , but does answer your question) - You can
jmp <mem>
if you wanted, but that's not all that different fromjmp <reg>
All of these are possible and solve your problem, but are all alike. I guess this illustrates my confusion on why you would want to do what you're asking. You have to have some way to choose which method to call (the vtable) and some way to transfer execution to the method (using jmp
or call
).
The only other possible way to do this would be to fiddle with the register that is used to point to the next command in the execution chain (EIP
in x86). Whether you can or should is another question. I suppose if you were intimately knowledgeable of the architecture and weren't worried about it changing you could do that.
Yes
You may want to consider changing your question however
Consider this C++ code where Derived
is obviously polymorphic (yes, I didn't delete the object):
class Base
{
public: int foo(){ return 1; }
};
class Derived: public Base
{
public: int foo(){ return 2; };
};
int main()
{
Base* b = new Derived();
return b->foo(); //Returns 1
}
The assembly generated by gcc is
main:
.LFB2:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $48, %rsp
.seh_stackalloc 48
.seh_endprologue
call __main
movl $1, %ecx
call _Znwm
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rcx
call _ZN4Base3fooEv
addq $48, %rsp
popq %rbp
ret
As you can see there is no indirect jump/call.
That's because polymorphism is not the point here (though it is necessary), virtual methods are.
Then the answer becames
No
If by indirect jump/call you means every technique that use a runtime value for computing the target of the jump/call (so including things like ret
, call []
, call reg
, jr
, jalr
).
Consider this source
#include <iostream>
class Base
{
public: virtual int foo(){ return 1; }
};
class Derived: public Base
{
public: int foo(){ return 2; };
};
class Derived2: public Base
{
public: int foo(){ return 3; };
};
int main()
{
int a;
Base* b = 0; //don't remember the header for std::nullptr right now...
std::cin >> a;
if (a > 241)
b = new Derived();
else
b = new Derived2();
return b->foo(); //Returns what?
}
The result depends on the user input, an archetypal runtime value, so must be the called routine and no static address would do.
Note that in this case the compiler could use a jump toward calls with static address (as either Derived2::foo
or Derived::foo
is called) but this is not in general possible (you may have multiple object files without the source, you may have an aliasing pointer, the pointer b
could be set by an external library, and so on).
精彩评论